diff --git a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py index 3f6d0ca47592..189dd4ce768e 100644 --- a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py +++ b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py @@ -9,6 +9,13 @@ from _pydevd_bundle import pydevd_xml from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items from _pydevd_bundle.pydevd_utils import to_string +try: + import cStringIO as StringIO #may not always be available @UnusedImport +except: + try: + import StringIO #@Reimport + except: + import io as StringIO # ======================================================================================================================= # Null @@ -450,15 +457,17 @@ class BaseInterpreterInterface: return True def getFrame(self): + xml = StringIO.StringIO() hidden_ns = self.get_ipython_hidden_vars_dict() - xml = "" - xml += pydevd_xml.frame_vars_to_xml(self.get_namespace(), hidden_ns) - xml += "" + xml.write("") + xml.write(pydevd_xml.frame_vars_to_xml(self.get_namespace(), hidden_ns)) + xml.write("") - return xml + return xml.getvalue() def getVariable(self, attributes): - xml = "" + xml = StringIO.StringIO() + xml.write("") valDict = pydevd_vars.resolve_var(self.get_namespace(), attributes) if valDict is None: valDict = {} @@ -466,11 +475,13 @@ class BaseInterpreterInterface: keys = valDict.keys() for k in keys: - xml += pydevd_vars.var_to_xml(valDict[k], to_string(k)) + val = valDict[k] + evaluate_full_value = pydevd_xml.should_evaluate_full_value(val) + xml.write(pydevd_vars.var_to_xml(val, k, evaluate_full_value=evaluate_full_value)) - xml += "" + xml.write("") - return xml + return xml.getvalue() def getArray(self, attr, roffset, coffset, rows, cols, format): name = attr.split("\t")[-1] @@ -478,14 +489,21 @@ class BaseInterpreterInterface: return pydevd_vars.table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format) def evaluate(self, expression): - xml = "" + xml = StringIO.StringIO() + xml.write("") result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace()) + xml.write(pydevd_vars.var_to_xml(result, expression)) + xml.write("") + return xml.getvalue() - xml += pydevd_vars.var_to_xml(result, expression) - - xml += "" - - return xml + def loadFullValue(self, expressions): + xml = StringIO.StringIO() + xml.write("") + for expression in expressions: + result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace()) + xml.write(pydevd_vars.var_to_xml(result, expression, evaluate_full_value=True)) + xml.write("") + return xml.getvalue() def changeVariable(self, attr, value): def do_change_variable(): diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py index 9e4ba13eb9bb..2e8dc5b3557c 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py @@ -89,6 +89,13 @@ from _pydevd_bundle.pydevd_tracing import get_exception_traceback_str from _pydevd_bundle import pydevd_console from _pydev_bundle.pydev_monkey import disable_trace_thread_modules, enable_trace_thread_modules +try: + import cStringIO as StringIO #may not always be available @UnusedImport +except: + try: + import StringIO #@Reimport + except: + import io as StringIO CMD_RUN = 101 @@ -148,6 +155,7 @@ CMD_GET_DESCRIPTION = 148 CMD_PROCESS_CREATED = 149 CMD_SHOW_CYTHON_WARNING = 150 +CMD_LOAD_FULL_VALUE = 151 CMD_VERSION = 501 CMD_RETURN = 502 @@ -207,6 +215,7 @@ ID_TO_MEANING = { '149': 'CMD_PROCESS_CREATED', '150': 'CMD_SHOW_CYTHON_WARNING', + '151': 'CMD_LOAD_FULL_VALUE', '501': 'CMD_VERSION', '502': 'CMD_RETURN', @@ -844,6 +853,12 @@ class NetCommandFactory: except: return self.make_error_message(0, get_exception_traceback_str()) + def make_load_full_value_message(self, seq, payload): + try: + return NetCommand(CMD_LOAD_FULL_VALUE, seq, payload) + except Exception: + return self.make_error_message(seq, get_exception_traceback_str()) + def make_exit_message(self): try: net = NetCommand(CMD_EXIT, 0, '') @@ -1009,7 +1024,8 @@ class InternalGetVariable(InternalThreadCommand): def do_it(self, dbg): """ Converts request into python variable """ try: - xml = "" + xml = StringIO.StringIO() + xml.write("") _typeName, valDict = pydevd_vars.resolve_compound_variable(self.thread_id, self.frame_id, self.scope, self.attributes) if valDict is None: valDict = {} @@ -1025,10 +1041,13 @@ class InternalGetVariable(InternalThreadCommand): keys = sorted(keys, cmp=compare_object_attrs) #Jython 2.1 does not have it (and all must be compared as strings). for k in keys: - xml += pydevd_xml.var_to_xml(valDict[k], to_string(k)) + val = valDict[k] + evaluate_full_value = pydevd_xml.should_evaluate_full_value(val) + xml.write(pydevd_xml.var_to_xml(val, k, evaluate_full_value=evaluate_full_value)) - xml += "" - cmd = dbg.cmd_factory.make_get_variable_message(self.sequence, xml) + xml.write("") + cmd = dbg.cmd_factory.make_get_variable_message(self.sequence, xml.getvalue()) + xml.close() dbg.writer.add_command(cmd) except Exception: cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving variables " + get_exception_traceback_str()) @@ -1442,6 +1461,61 @@ class InternalConsoleExec(InternalThreadCommand): sys.stdout.flush() +#======================================================================================================================= +# InternalLoadFullValue +#======================================================================================================================= +class InternalLoadFullValue(InternalThreadCommand): + """ changes the value of a variable """ + def __init__(self, seq, thread_id, frame_id, vars): + self.sequence = seq + self.thread_id = thread_id + self.frame_id = frame_id + self.vars = vars + + def do_it(self, dbg): + """ Converts request into python variable """ + try: + var_objects = [] + for variable in self.vars: + variable = variable.strip() + if len(variable) > 0: + if '\t' in variable: # there are attributes beyond scope + scope, attrs = variable.split('\t', 1) + name = attrs[0] + else: + scope, attrs = (variable, None) + name = scope + + var_obj = pydevd_vars.getVariable(self.thread_id, self.frame_id, scope, attrs) + var_objects.append((var_obj, name)) + + t = GetValueAsyncThread(dbg, self.sequence, var_objects) + t.start() + except: + exc = get_exception_traceback_str() + sys.stderr.write('%s\n' % (exc,)) + cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error evaluating variable %s " % exc) + dbg.writer.add_command(cmd) + + +class GetValueAsyncThread(PyDBDaemonThread): + def __init__(self, py_db, seq, var_objects): + PyDBDaemonThread.__init__(self) + self.py_db = py_db + self.seq = seq + self.var_objs = var_objects + + def _on_run(self): + xml = StringIO.StringIO() + xml.write("") + for (var_obj, name) in self.var_objs: + xml.write(pydevd_xml.var_to_xml(var_obj, name, evaluate_full_value=True)) + xml.write("") + cmd = self.py_db.cmd_factory.make_load_full_value_message(self.seq, xml.getvalue()) + xml.close() + self.py_db.writer.add_command(cmd) + + #======================================================================================================================= # pydevd_find_thread_by_id #======================================================================================================================= diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py index 07e122a411d5..9a6337d1d143 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py @@ -110,6 +110,11 @@ USE_LIB_COPY = SUPPORT_GEVENT and \ 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" +NEXT_VALUE_SEPARATOR = "__pydev_val__" +BUILTINS_MODULE_NAME = '__builtin__' if IS_PY2 else 'builtins' + def protect_libraries_from_patching(): """ diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py b/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py index f326086adaed..640978b7f9c2 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py @@ -17,9 +17,10 @@ from _pydevd_bundle.pydevd_comm import CMD_RUN, CMD_VERSION, CMD_LIST_THREADS, C CMD_REMOVE_EXCEPTION_BREAK, CMD_LOAD_SOURCE, CMD_ADD_DJANGO_EXCEPTION_BREAK, CMD_REMOVE_DJANGO_EXCEPTION_BREAK, \ CMD_EVALUATE_CONSOLE_EXPRESSION, InternalEvaluateConsoleExpression, InternalConsoleGetCompletions, \ CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \ - CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription + CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription, InternalLoadFullValue, \ + CMD_LOAD_FULL_VALUE from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_contains, dict_keys, \ - STATE_RUN + STATE_RUN, NEXT_VALUE_SEPARATOR def process_net_command(py_db, cmd_id, seq, text): @@ -220,6 +221,16 @@ def process_net_command(py_db, cmd_id, seq, text): except: traceback.print_exc() + elif cmd_id == CMD_LOAD_FULL_VALUE: + try: + thread_id, frame_id, scopeattrs = text.split('\t', 2) + vars = scopeattrs.split(NEXT_VALUE_SEPARATOR) + + int_cmd = InternalLoadFullValue(seq, thread_id, frame_id, vars) + py_db.post_internal_command(int_cmd, thread_id) + except: + traceback.print_exc() + elif cmd_id == CMD_GET_COMPLETIONS: # we received some command to get a variable # the text is: thread_id\tframe_id\tactivation token diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py b/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py index dfe3855b0bd2..886e50d806a9 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_resolver.py @@ -349,8 +349,8 @@ class SetResolver: d = {} i = 0 for item in var: - i+= 1 - d[id(item)] = item + i += 1 + d[str(id(item))] = item if i > MAX_ITEMS_TO_HANDLE: d[TOO_LARGE_ATTR] = TOO_LARGE_MSG diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py index e6a08b417be5..638e685a7e3f 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py @@ -3,7 +3,8 @@ import traceback from _pydevd_bundle import pydevd_resolver import sys from _pydevd_bundle.pydevd_constants import dict_contains, dict_iter_items, dict_keys, IS_PY3K, \ - MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT + IS_PY2, BUILTINS_MODULE_NAME, MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT, LOAD_VALUES_ASYNC, \ + DEFAULT_VALUE from _pydev_bundle.pydev_imports import quote @@ -162,14 +163,22 @@ def get_type(o): return (type_object, type_name, pydevd_resolver.defaultResolver) -def return_values_from_dict_to_xml(return_dict): +def is_builtin(x): + return getattr(x, '__module__', None) == BUILTINS_MODULE_NAME + + +def should_evaluate_full_value(val): + return not LOAD_VALUES_ASYNC or (is_builtin(type(val)) and not isinstance(val, (list, tuple, dict))) + + +def return_values_from_dict_to_xml(return_dict, eval_full_val=True): res = "" for name, val in dict_iter_items(return_dict): - res += var_to_xml(val, name, additionalInXml=' isRetVal="True"') + res += var_to_xml(val, name, additionalInXml=' isRetVal="True"', evaluate_full_value=eval_full_val) return res -def frame_vars_to_xml(frame_f_locals, hidden_ns=None): +def frame_vars_to_xml(frame_f_locals, hidden_ns=None, dbg=None, thread_id=None, frame_id=None): """ dumps frame variables to XML """ @@ -184,13 +193,15 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None): for k in keys: try: v = frame_f_locals[k] + eval_full_val = should_evaluate_full_value(v) + if k == RETURN_VALUES_DICT: - xml += return_values_from_dict_to_xml(v) + xml += return_values_from_dict_to_xml(v, eval_full_val=eval_full_val) else: if hidden_ns is not None and dict_contains(hidden_ns, k): - xml += var_to_xml(v, str(k), additionalInXml=' isIPythonHidden="True"') + xml += var_to_xml(v, str(k), additionalInXml=' isIPythonHidden="True"', evaluate_full_value=eval_full_val) else: - xml += var_to_xml(v, str(k)) + xml += var_to_xml(v, str(k), evaluate_full_value=eval_full_val) except Exception: traceback.print_exc() pydev_log.error("Unexpected error, recovered safely.\n") @@ -198,7 +209,7 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None): return xml -def var_to_xml(val, name, doTrim=True, additionalInXml='', return_value=False, ipython_hidden=False): +def var_to_xml(val, name, doTrim=True, additionalInXml='', evaluate_full_value=True): """ single variable or dictionary to xml representation """ is_exception_on_eval = isinstance(val, ExceptionOnEvaluate) @@ -210,43 +221,45 @@ def var_to_xml(val, name, doTrim=True, additionalInXml='', return_value=False, i _type, typeName, resolver = get_type(v) type_qualifier = getattr(_type, "__module__", "") - do_not_call_value_str = resolver is not None and resolver.use_value_repr_instead_of_str - try: - if hasattr(v, '__class__'): - if v.__class__ == frame_type: - value = pydevd_resolver.frameResolver.get_frame_name(v) - - elif v.__class__ in (list, tuple): - if len(v) > 300: - value = '%s: %s' % (str(v.__class__), '' % (len(v),)) - else: - value = '%s: %s' % (str(v.__class__), v) - else: - try: - cName = str(v.__class__) - if cName.find('.') != -1: - cName = cName.split('.')[-1] - - elif cName.find("'") != -1: #does not have '.' (could be something like ) - cName = cName[cName.index("'") + 1:] - - if cName.endswith("'>"): - cName = cName[:-2] - except: - cName = str(v.__class__) - - if do_not_call_value_str: - value = '%s: %r' % (cName, v) - else: - value = '%s: %s' % (cName, v) - else: - value = str(v) - except: + if not evaluate_full_value: + value = DEFAULT_VALUE + else: try: - value = repr(v) + if hasattr(v, '__class__'): + if v.__class__ == frame_type: + value = pydevd_resolver.frameResolver.get_frame_name(v) + + elif v.__class__ in (list, tuple, dict): + if len(v) > 300: + value = '%s: %s' % (str(v.__class__), '' % (len(v),)) + else: + value = '%s: %s' % (str(v.__class__), v) + else: + try: + cName = str(v.__class__) + if cName.find('.') != -1: + cName = cName.split('.')[-1] + + elif cName.find("'") != -1: #does not have '.' (could be something like ) + cName = cName[cName.index("'") + 1:] + + if cName.endswith("'>"): + cName = cName[:-2] + except: + cName = str(v.__class__) + + if resolver is not None and resolver.use_value_repr_instead_of_str: + value = '%s: %r' % (cName, v) + else: + value = '%s: %s' % (cName, v) + else: + value = str(v) except: - value = 'Unable to get repr for %s' % v.__class__ + try: + value = repr(v) + except: + value = 'Unable to get repr for %s' % v.__class__ try: name = quote(name, '/>_= ') #TODO: Fix PY-5834 without using quote diff --git a/python/helpers/pydev/pydevconsole.py b/python/helpers/pydev/pydevconsole.py index b50f3d4e3b6e..5c94eea9681a 100644 --- a/python/helpers/pydev/pydevconsole.py +++ b/python/helpers/pydev/pydevconsole.py @@ -315,6 +315,7 @@ def start_console_server(host, port, interpreter): server.register_function(interpreter.getArray) server.register_function(interpreter.evaluate) server.register_function(interpreter.ShowConsole) + server.register_function(interpreter.loadFullValue) # Functions for GUI main loop integration server.register_function(interpreter.enableGui) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index 546da2bd248d..98b18f992fb6 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -6,11 +6,14 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.xdebugger.frame.*; +import com.jetbrains.python.debugger.pydev.PyDebugCallback; import com.jetbrains.python.debugger.pydev.PyVariableLocator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,31 +28,45 @@ public class PyDebugValue extends XNamedValue { public static final int MAX_VALUE = 256; public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict"; + public static final String DEFAULT_VALUE_ASYNC = "__pydevd_value_async"; - private String myTempName = null; - private final String myType; - private final String myTypeQualifier; - private final String myValue; + private @Nullable String myTempName = null; + private final @Nullable String myType; + private final @Nullable String myTypeQualifier; + private @Nullable String myValue; private final boolean myContainer; private final boolean myIsReturnedVal; private final boolean myIsIPythonHidden; - private final PyDebugValue myParent; - private String myId = null; - - private final PyFrameAccessor myFrameAccessor; - - private PyVariableLocator myVariableLocator; - + private @Nullable PyDebugValue myParent; + private @Nullable String myId = null; + private boolean myLoadValueAsync; + private @NotNull PyFrameAccessor myFrameAccessor; + private @Nullable PyVariableLocator myVariableLocator; + private volatile @Nullable XValueNode myLastNode = null; private final boolean myErrorOnEval; - public PyDebugValue(@NotNull final String name, final String type, String typeQualifier, final String value, final boolean container, - boolean isReturnedVal, boolean isIPythonHidden, boolean errorOnEval, final PyFrameAccessor frameAccessor) { + public PyDebugValue(@NotNull final String name, + @Nullable final String type, + @Nullable String typeQualifier, + @Nullable final String value, + final boolean container, + boolean isReturnedVal, + boolean isIPythonHidden, + boolean errorOnEval, + @NotNull final PyFrameAccessor frameAccessor) { this(name, type, typeQualifier, value, container, isReturnedVal, isIPythonHidden, errorOnEval, null, frameAccessor); } - public PyDebugValue(@NotNull final String name, final String type, String typeQualifier, final String value, final boolean container, - boolean isReturnedVal, boolean isIPythonHidden, boolean errorOnEval, final PyDebugValue parent, - final PyFrameAccessor frameAccessor) { + public PyDebugValue(@NotNull final String name, + @Nullable final String type, + @Nullable String typeQualifier, + @Nullable final String value, + final boolean container, + boolean isReturnedVal, + boolean isIPythonHidden, + boolean errorOnEval, + @Nullable final PyDebugValue parent, + @NotNull final PyFrameAccessor frameAccessor) { super(name); myType = type; myTypeQualifier = Strings.isNullOrEmpty(typeQualifier) ? null : typeQualifier; @@ -60,20 +77,43 @@ public class PyDebugValue extends XNamedValue { myErrorOnEval = errorOnEval; myParent = parent; myFrameAccessor = frameAccessor; + myLoadValueAsync = false; + if (DEFAULT_VALUE_ASYNC.equals(myValue)) { + myLoadValueAsync = true; + setValue(" "); + } } + 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()); + setTempName(value.getTempName()); + } + + public PyDebugValue(@NotNull PyDebugValue value) { + this(value, value.getName()); + } + + @Nullable public String getTempName() { return myTempName != null ? myTempName : myName; } - public void setTempName(String tempName) { + public void setTempName(@Nullable String tempName) { myTempName = tempName; } + @Nullable public String getType() { return myType; } + public void setValue(@Nullable String newValue) { + myValue = newValue; + } + + @Nullable public String getValue() { return myValue; } @@ -94,27 +134,42 @@ public class PyDebugValue extends XNamedValue { return myErrorOnEval; } - public PyDebugValue setParent(@Nullable PyDebugValue parent) { - return new PyDebugValue(myName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden, myErrorOnEval, - parent, myFrameAccessor); - } - + @Nullable public PyDebugValue getParent() { return myParent; } + public void setParent(@Nullable PyDebugValue parent) { + myParent = parent; + } + + @Nullable public PyDebugValue getTopParent() { return myParent == null ? this : myParent.getTopParent(); } + public boolean isLoadValueAsync() { + return myLoadValueAsync; + } + + public void setLoadValueAsync(boolean loadValueAsync) { + myLoadValueAsync = loadValueAsync; + } + + @Nullable + public XValueNode getLastNode() { + return myLastNode; + } + + @NotNull @Override public String getEvaluationExpression() { StringBuilder stringBuilder = new StringBuilder(); buildExpression(stringBuilder); - return stringBuilder.toString(); + return wrapWithPrefix(stringBuilder.toString()); } - void buildExpression(StringBuilder result) { + void buildExpression(@NotNull StringBuilder result) { if (myParent == null) { result.append(getTempName()); } @@ -139,10 +194,12 @@ public class PyDebugValue extends XNamedValue { } } + @NotNull public String getFullName() { return wrapWithPrefix(getName()); } + @NotNull private static String removeId(@NotNull String name) { if (name.indexOf('(') != -1) { name = name.substring(0, name.indexOf('(')).trim(); @@ -151,6 +208,7 @@ public class PyDebugValue extends XNamedValue { return name; } + @NotNull private static String removeLeadingZeros(@NotNull String name) { //bugs.python.org/issue15254: "0" prefix for octal while (name.length() > 1 && name.startsWith("0")) { @@ -159,25 +217,12 @@ public class PyDebugValue extends XNamedValue { return name; } - private static boolean isLen(String name) { + private static boolean isLen(@NotNull String name) { return "__len__".equals(name); } - private static boolean isCollection(@NotNull PyDebugValue parent) { - String type = parent.getType(); - return type.equals("dict") || type.equals("list"); - } - - private static String getChildNamePresentation(@NotNull PyDebugValue parent, @NotNull String childName) { - if (isCollection(parent)) { - return "[".concat(removeId(childName)).concat("]"); - } - else { - return ".".concat(childName); - } - } - - private String wrapWithPrefix(String name) { + @NotNull + private String wrapWithPrefix(@NotNull String name) { if (isReturnedVal()) { // return values are saved in dictionary on Python side, so the variable's name should be transformed return RETURN_VALUES_PREFIX + "[\"" + name + "\"]"; @@ -187,18 +232,6 @@ public class PyDebugValue extends XNamedValue { } } - private String getFullTreeName() { - String result = ""; - String curNodeName = myName; - PyDebugValue parent = myParent; - while (parent != null) { - result = getChildNamePresentation(parent, curNodeName).concat(result); - curNodeName = parent.getName(); - parent = parent.getParent(); - } - return wrapWithPrefix(curNodeName.concat(result)); - } - @Override public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) { String value = PyTypeHandler.format(this); @@ -209,13 +242,94 @@ public class PyDebugValue extends XNamedValue { node.setPresentation(getValueIcon(), myType, value, myContainer); } - private void setFullValueEvaluator(XValueNode node, String value) { - String treeName = getFullTreeName(); + public void updateNodeValueAfterLoading(@NotNull XValueNode node, @NotNull String value, @NotNull String linkText) { + node.setPresentation(getValueIcon(), myType, value, myContainer); + if (value.length() >= MAX_VALUE) { + node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, getEvaluationExpression())); + } + else { + node.setFullValueEvaluator(new XFullValueEvaluator() { + @Override + public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) { + callback.evaluated(value); + } + + @Override + public String getLinkText() { + return linkText; + } + + @Override + public boolean isShowValuePopup() { + return false; + } + }); + } + } + + @NotNull + public PyDebugCallback createDebugValueCallback() { + return new PyDebugCallback() { + @Override + public void ok(String value) { + myLoadValueAsync = false; + myValue = value; + XValueNode node = myLastNode; + if (node != null && !node.isObsolete()) { + updateNodeValueAfterLoading(node, value, ""); + } + } + + @Override + public void error(PyDebuggerException exception) { + LOG.error(exception.getMessage()); + } + }; + } + + public boolean isNumericContainer() { + return EVALUATOR_POSTFIXES.get(myType) != null; + } + + @NotNull + public static List> getAsyncValuesFromChildren(@NotNull XValueChildrenList childrenList) { + List> variables = new ArrayList<>(); + for (int i = 0; i < childrenList.size(); i++) { + XValue value = childrenList.getValue(i); + if (value instanceof PyDebugValue) { + PyDebugValue debugValue = (PyDebugValue)value; + if (debugValue.isLoadValueAsync() && !debugValue.isNumericContainer()) { + variables.add(new PyFrameAccessor.PyAsyncValue<>(debugValue, debugValue.createDebugValueCallback())); + } + } + } + return variables; + } + + public static void getAsyncValues(@NotNull PyFrameAccessor frameAccessor, @NotNull XValueChildrenList childrenList) { + List> variables = getAsyncValuesFromChildren(childrenList); + int cores = Runtime.getRuntime().availableProcessors(); + int chunkSize = Math.max(1, variables.size() / cores); + int left = 0; + int right = Math.min(chunkSize, variables.size()); + while (left < variables.size()) { + frameAccessor.loadAsyncVariablesValues(variables.subList(left, right)); + left = right; + right = Math.min(right + chunkSize, variables.size()); + } + } + + private void setFullValueEvaluator(@NotNull XValueNode node, @NotNull String value) { + String treeName = getEvaluationExpression(); String postfix = EVALUATOR_POSTFIXES.get(myType); if (postfix == null) { if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName)); } + if (myLoadValueAsync) { + node.setFullValueEvaluator(new PyLoadingValueEvaluator("... Loading Value", myFrameAccessor, treeName)); + myLastNode = node; + } return; } String linkText = "...View as " + postfix; @@ -226,12 +340,11 @@ public class PyDebugValue extends XNamedValue { public void computeChildren(@NotNull final XCompositeNode node) { if (node.isObsolete()) return; ApplicationManager.getApplication().executeOnPooledThread(() -> { - if (myFrameAccessor == null) return; - try { final XValueChildrenList values = myFrameAccessor.loadVariable(this); if (!node.isObsolete()) { node.addChildren(values, true); + getAsyncValues(myFrameAccessor, values); } } catch (PyDebuggerException e) { @@ -243,6 +356,7 @@ public class PyDebugValue extends XNamedValue { }); } + @NotNull @Override public XValueModifier getModifier() { return new PyValueModifier(myFrameAccessor, this); @@ -260,13 +374,6 @@ public class PyDebugValue extends XNamedValue { } } - public PyDebugValue setName(String newName) { - PyDebugValue value = new PyDebugValue(newName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden, - myErrorOnEval, myParent, myFrameAccessor); - value.setTempName(myTempName); - return value; - } - @Nullable @Override public XReferrersProvider getReferrersProvider() { @@ -282,23 +389,30 @@ public class PyDebugValue extends XNamedValue { } } + @NotNull public PyFrameAccessor getFrameAccessor() { return myFrameAccessor; } + public void setFrameAccessor(@NotNull PyFrameAccessor frameAccessor) { + myFrameAccessor = frameAccessor; + } + + @Nullable public PyVariableLocator getVariableLocator() { return myVariableLocator; } - public void setVariableLocator(PyVariableLocator variableLocator) { + public void setVariableLocator(@Nullable PyVariableLocator variableLocator) { myVariableLocator = variableLocator; } + @Nullable public String getId() { return myId; } - public void setId(String id) { + public void setId(@Nullable String id) { myId = id; } @@ -312,8 +426,7 @@ public class PyDebugValue extends XNamedValue { if (myParent == null) { navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, null)); } - else - { + else { navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, myParent.getDeclaringType())); } } @@ -324,32 +437,33 @@ public class PyDebugValue extends XNamedValue { } private static final Pattern IS_TYPE_DECLARATION = Pattern.compile("<(?:class|type)\\s*'(?.*?)'>"); + @Override public void computeTypeSourcePosition(@NotNull XNavigatable navigatable) { - String lookupType = getDeclaringType(); navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForType(lookupType)); } + @Nullable protected final String getDeclaringType() { String lookupType = getQualifiedType(); - if (!Strings.isNullOrEmpty(myValue)) - { + if (!Strings.isNullOrEmpty(myValue)) { Matcher matcher = IS_TYPE_DECLARATION.matcher(myValue); - if (matcher.matches()) - { + if (matcher.matches()) { lookupType = matcher.group("TYPE"); } } return lookupType; } + @Nullable public String getQualifiedType() { if (Strings.isNullOrEmpty(myType)) return null; return (myTypeQualifier == null) ? myType : (myTypeQualifier + "." + myType); } + @Nullable public String getTypeQualifier() { return myTypeQualifier; } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java index b75e2d374eff..afae380d7b92 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java @@ -2,9 +2,12 @@ package com.jetbrains.python.debugger; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.XValueChildrenList; +import com.jetbrains.python.debugger.pydev.PyDebugCallback; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * Facade to access python variables frame * @@ -26,12 +29,38 @@ public interface PyFrameAccessor { ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException; @Nullable - XSourcePosition getSourcePositionForName(String name, String parentType); + XSourcePosition getSourcePositionForName(@Nullable String name, @Nullable String parentType); @Nullable XSourcePosition getSourcePositionForType(String type); - default void showNumericContainer(PyDebugValue value) {} + default void showNumericContainer(@NotNull PyDebugValue value) {} default void addFrameListener(@NotNull PyFrameListener listener) {} + + default void loadAsyncVariablesValues(@NotNull final List> pyAsyncValues) {} + + default boolean isCurrentFrameCached() { + return false; + } + + class PyAsyncValue { + private final @NotNull PyDebugValue myDebugValue; + private final @NotNull PyDebugCallback myCallback; + + public PyAsyncValue(@NotNull PyDebugValue debugValue, @NotNull PyDebugCallback callback) { + myDebugValue = debugValue; + myCallback = callback; + } + + @NotNull + public PyDebugValue getDebugValue() { + return myDebugValue; + } + + @NotNull + public PyDebugCallback getCallback() { + return myCallback; + } + } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java index fc3cbd77b55a..a1ab5b616427 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java @@ -1,5 +1,6 @@ package com.jetbrains.python.debugger; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.xdebugger.frame.XFullValueEvaluator; import org.jetbrains.annotations.NotNull; @@ -36,14 +37,19 @@ public class PyFullValueEvaluator extends XFullValueEvaluator { return; } - try { - final PyDebugValue value = myDebugProcess.evaluate(expression, false, false); - callback.evaluated(value.getValue()); - showCustomPopup(myDebugProcess, value); - } - catch (PyDebuggerException e) { - callback.errorOccurred(e.getTracebackError()); - } + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + final PyDebugValue value = myDebugProcess.evaluate(expression, false, false); + if (value.getValue() == null) { + throw new PyDebuggerException("Failed to Load Value"); + } + callback.evaluated(value.getValue()); + ApplicationManager.getApplication().invokeLater(() -> showCustomPopup(myDebugProcess, value)); + } + catch (PyDebuggerException e) { + callback.errorOccurred(e.getTracebackError()); + } + }); } protected void showCustomPopup(PyFrameAccessor debugProcess, PyDebugValue debugValue) { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyLoadingValueEvaluator.java b/python/pydevSrc/com/jetbrains/python/debugger/PyLoadingValueEvaluator.java new file mode 100644 index 000000000000..a80db51ca79f --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyLoadingValueEvaluator.java @@ -0,0 +1,19 @@ +package com.jetbrains.python.debugger; + +import org.jetbrains.annotations.NotNull; + +public class PyLoadingValueEvaluator extends PyFullValueEvaluator { + protected PyLoadingValueEvaluator(@NotNull String linkText, @NotNull PyFrameAccessor debugProcess, @NotNull String expression) { + super(linkText, debugProcess, expression); + } + + @Override + public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) { + callback.evaluated("... Loading value"); + } + + @Override + public boolean isShowValuePopup() { + return false; + } +} diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java index 884398894109..0ff32f25ff05 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java @@ -57,6 +57,7 @@ public abstract class AbstractCommand { public static final int PROCESS_CREATED = 149; public static final int SHOW_CYTHON_WARNING = 150; + public static final int LOAD_FULL_VALUE = 151; public static final int ERROR = 901; @@ -188,7 +189,7 @@ public abstract class AbstractCommand { } - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { if (response.getCommand() >= 900 && response.getCommand() < 1000) { throw new PyDebuggerException(response.getPayload()); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java index c9f53babdff0..d959267dec73 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java @@ -4,6 +4,7 @@ package com.jetbrains.python.debugger.pydev; import com.jetbrains.python.debugger.IPyDebugProcess; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; public class ChangeVariableCommand extends AbstractFrameCommand { @@ -32,9 +33,10 @@ public class ChangeVariableCommand extends AbstractFrameCommand { return true; } - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); - myNewValue = ProtocolParser.parseValue(response.getPayload(), myDebugProcess).setName(myVariableName); + PyDebugValue returnedValue = ProtocolParser.parseValue(response.getPayload(), myDebugProcess); + myNewValue = new PyDebugValue(returnedValue, myVariableName); } public PyDebugValue getNewValue() { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java index b8139653edf3..05acb5440416 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java @@ -187,6 +187,13 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger { return debugger(threadId).changeVariable(threadId, frameId, var, value); } + @Override + public void loadFullVariableValues(@NotNull String threadId, + @NotNull String frameId, + @NotNull List> vars) throws PyDebuggerException { + debugger(threadId).loadFullVariableValues(threadId, frameId, vars); + } + @Override public String loadSource(String path) { return myMainDebugger.loadSource(path); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java index 373c02a28c05..8895d665a580 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java @@ -3,6 +3,7 @@ package com.jetbrains.python.debugger.pydev; import com.jetbrains.python.debugger.IPyDebugProcess; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; public class EvaluateCommand extends AbstractFrameCommand { @@ -37,10 +38,10 @@ public class EvaluateCommand extends AbstractFrameCommand { } @Override - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); final PyDebugValue value = ProtocolParser.parseValue(response.getPayload(), myDebugProcess); - myValue = value.setName((myExecute ? "" : myExpression)); + myValue = new PyDebugValue(value, myExecute ? "" : myExpression); if (!myTempName.isEmpty()) { myValue.setTempName(myTempName); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java index da19bf2381a6..1487d1ad32a5 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java @@ -3,6 +3,7 @@ package com.jetbrains.python.debugger.pydev; import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; /** * @author amarch @@ -51,7 +52,7 @@ public class GetArrayCommand extends GetFrameCommand { } @Override - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { if (response.getCommand() >= 900 && response.getCommand() < 1000) { throw new PyDebuggerException(response.getPayload()); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetCompletionsCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetCompletionsCommand.java index e7e56653670c..35d3bbf86f40 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetCompletionsCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetCompletionsCommand.java @@ -31,7 +31,7 @@ public class GetCompletionsCommand extends AbstractFrameCommand { } @Override - protected void processResponse(ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); try { myCompletions = PydevXmlUtils.xmlToCompletions(response.getPayload(), myActionToken); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java index ca216c7eb02e..07b53bd4d09f 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java @@ -2,6 +2,7 @@ package com.jetbrains.python.debugger.pydev; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; /** * @author traff @@ -23,7 +24,7 @@ public class GetDescriptionCommand extends AbstractFrameCommand { } @Override - protected void processResponse(ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); try { PyDebugValue pyDebugValue = ProtocolParser.parseValue(response.getPayload(), getDebugger().getDebugProcess()); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java index e9ac6b810f01..a520f42e24ca 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java @@ -4,6 +4,7 @@ import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.python.debugger.IPyDebugProcess; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; import java.util.List; @@ -34,7 +35,7 @@ public class GetFrameCommand extends AbstractFrameCommand { } @Override - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); final List values = ProtocolParser.parseValues(response.getPayload(), myDebugProcess); myFrameVariables = new XValueChildrenList(values.size()); @@ -47,8 +48,10 @@ public class GetFrameCommand extends AbstractFrameCommand { } protected PyDebugValue extend(final PyDebugValue value) { - return new PyDebugValue(value.getName(), value.getType(), value.getTypeQualifier(), value.getValue(), value.isContainer(), - value.isReturnedVal(), value.isIPythonHidden(), value.isErrorOnEval(), null, myDebugProcess); + PyDebugValue debugValue = new PyDebugValue(value); + debugValue.setParent(null); + debugValue.setFrameAccessor(myDebugProcess); + return debugValue; } public XValueChildrenList getVariables() { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java index 6acf73d8dd0a..506e52438732 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java @@ -50,8 +50,9 @@ public class GetVariableCommand extends GetFrameCommand { @Override protected PyDebugValue extend(final PyDebugValue value) { - return new PyDebugValue(value.getName(), value.getType(), value.getTypeQualifier(), value.getValue(), value.isContainer(), - value.isReturnedVal(), value.isIPythonHidden(), value.isErrorOnEval(), myParent, - myDebugProcess); + PyDebugValue debugValue = new PyDebugValue(value); + debugValue.setParent(myParent); + debugValue.setFrameAccessor(myDebugProcess); + return debugValue; } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadFullValueCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadFullValueCommand.java new file mode 100644 index 000000000000..eef864d0c64d --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadFullValueCommand.java @@ -0,0 +1,72 @@ +package com.jetbrains.python.debugger.pydev; + +import com.jetbrains.python.debugger.IPyDebugProcess; +import com.jetbrains.python.debugger.PyDebugValue; +import com.jetbrains.python.debugger.PyDebuggerException; +import com.jetbrains.python.debugger.PyFrameAccessor; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + + +public class LoadFullValueCommand extends AbstractFrameCommand { + private static String NEXT_VALUE_SEPARATOR = "__pydev_val__"; + private final @NotNull IPyDebugProcess myDebugProcess; + private final @NotNull List> myVars; + + public LoadFullValueCommand(final @NotNull RemoteDebugger debugger, + final @NotNull String threadId, + final @NotNull String frameId, + final @NotNull List> vars) { + super(debugger, LOAD_FULL_VALUE, threadId, frameId); + myDebugProcess = debugger.getDebugProcess(); + myVars = vars; + } + + @Override + public boolean isResponseExpected() { + return true; + } + + @Override + protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException { + super.processResponse(response); + try { + List debugValues = ProtocolParser.parseValues(response.getPayload(), myDebugProcess); + for (int i = 0; i < myVars.size(); ++i) { + PyDebugValue resultValue = debugValues.get(i); + myVars.get(i).getCallback().ok(resultValue.getValue()); + } + } + catch (Exception e) { + for (PyFrameAccessor.PyAsyncValue vars : myVars) { + vars.getCallback().error(new PyDebuggerException(response.getPayload())); + } + } + } + + @NotNull + private String buildPayloadForVar(@NotNull PyDebugValue var) { + StringBuilder sb = new StringBuilder(); + String varName = GetVariableCommand.composeName(var); + if (var.getVariableLocator() != null) { + sb.append(var.getVariableLocator().getThreadId()).append(var.getVariableLocator().getPyDBLocation()); + } + else if (varName.contains(GetVariableCommand.BY_ID)) { + sb.append(getThreadId()).append(varName); + } + else { + sb.append(varName); + } + return sb.toString(); + } + + @Override + protected void buildPayload(Payload payload) { + super.buildPayload(payload); + for (PyFrameAccessor.PyAsyncValue var : myVars) { + PyDebugValue debugValue = var.getDebugValue(); + payload.add("FRAME").add(buildPayloadForVar(debugValue)).add(NEXT_VALUE_SEPARATOR); + } + } +} diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java index cdafeae93a52..32581214cda2 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java @@ -22,7 +22,7 @@ public class LoadSourceCommand extends AbstractCommand { } @Override - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); myContent = ProtocolParser.parseSourceContent(response.getPayload()); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index 79e17ee5983b..d4644f66a0a1 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -224,6 +224,13 @@ public class MultiProcessDebugger implements ProcessDebugger { return debugger(threadId).changeVariable(threadId, frameId, var, value); } + @Override + public void loadFullVariableValues(@NotNull String threadId, + @NotNull String frameId, + @NotNull List> vars) throws PyDebuggerException { + debugger(threadId).loadFullVariableValues(threadId, frameId, vars); + } + @Override public String loadSource(String path) { return myMainDebugger.loadSource(path); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java index f21f99c9b667..05518561208e 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java @@ -50,6 +50,11 @@ public interface ProcessDebugger { PyDebugValue changeVariable(String threadId, String frameId, PyDebugValue var, String value) throws PyDebuggerException; + void loadFullVariableValues(@NotNull String threadId, + @NotNull String frameId, + @NotNull List> vars) + throws PyDebuggerException; + @Nullable String loadSource(String path); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index 3edc37ac5a52..bdbb27b99405 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -205,6 +205,13 @@ public class RemoteDebugger implements ProcessDebugger { return command.getNewValue(); } + public void loadFullVariableValues(@NotNull String threadId, + @NotNull String frameId, + @NotNull List> vars) throws PyDebuggerException { + final LoadFullValueCommand command = new LoadFullValueCommand(this, threadId, frameId, vars); + command.execute(); + } + @Override @Nullable public String loadSource(String path) { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetNextStatementCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetNextStatementCommand.java index 82d59cba8f24..0339c5b538f2 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetNextStatementCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetNextStatementCommand.java @@ -28,7 +28,7 @@ public class SetNextStatementCommand extends AbstractThreadCommand { } @Override - protected void processResponse(ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); try { Pair result = ProtocolParser.parseSetNextStatementCommand(response.getPayload()); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/VersionCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/VersionCommand.java index a458954374af..a5fe3e5bd50a 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/VersionCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/VersionCommand.java @@ -1,6 +1,7 @@ package com.jetbrains.python.debugger.pydev; import com.jetbrains.python.debugger.PyDebuggerException; +import org.jetbrains.annotations.NotNull; public class VersionCommand extends AbstractCommand { @@ -25,7 +26,7 @@ public class VersionCommand extends AbstractCommand { } @Override - protected void processResponse(final ProtocolFrame response) throws PyDebuggerException { + protected void processResponse(@NotNull final ProtocolFrame response) throws PyDebuggerException { super.processResponse(response); myRemoteVersion = response.getPayload(); } diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index ca7a1072a0f8..fafc9e7479cf 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -32,6 +32,7 @@ import com.intellij.util.WaitFor; import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.XValueChildrenList; +import com.intellij.xdebugger.frame.XValueNode; import com.jetbrains.python.console.parsing.PythonConsoleData; import com.jetbrains.python.console.pydev.*; import com.jetbrains.python.debugger.*; @@ -67,6 +68,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl private static final String CLOSE = "close"; private static final String EVALUATE = "evaluate"; private static final String GET_ARRAY = "getArray"; + private static final String LOAD_FULL_VALUE = "loadFullValue"; private static final String PYDEVD_EXTRA_ENVS = "PYDEVD_EXTRA_ENVS"; /** @@ -519,11 +521,63 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl return new XValueChildrenList(); } + @Override + public void loadAsyncVariablesValues(@NotNull List> pyAsyncValues) { + ApplicationManager.getApplication().executeOnPooledThread(() -> { + if (myClient != null) { + try { + List evaluationExpressions = new ArrayList<>(); + for (PyAsyncValue asyncValue : pyAsyncValues) { + evaluationExpressions.add(asyncValue.getDebugValue().getEvaluationExpression()); + } + Object ret = myClient.execute(LOAD_FULL_VALUE, new Object[]{evaluationExpressions.toArray()}); + + if (ret instanceof String) { + List debugValues = ProtocolParser.parseValues((String)ret, this); + for (int i = 0; i < pyAsyncValues.size(); ++i) { + pyAsyncValues.get(i).getCallback().ok(debugValues.get(i).getValue()); + } + } + else { + checkError(ret); + } + } + catch (PyDebuggerException e) { + if (myWebServer != null) { + LOG.error(e); + } + } + catch (XmlRpcException e) { + for (PyAsyncValue asyncValue : pyAsyncValues) { + PyDebugValue value = asyncValue.getDebugValue(); + 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"); + } + else { + LOG.error(e); + } + } + } + } + } + }); + } + private XValueChildrenList parseVars(String ret, PyDebugValue parent) throws PyDebuggerException { final List values = ProtocolParser.parseValues(ret, this); XValueChildrenList list = new XValueChildrenList(values.size()); for (PyDebugValue v : values) { - list.add(v.getName(), parent != null ? v.setParent(parent) : v); + PyDebugValue value; + if (parent != null) { + value = new PyDebugValue(v); + value.setParent(parent); + } + else { + value = v; + } + list.add(v.getName(), value); } return list; } @@ -689,7 +743,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl } @Override - public void showNumericContainer(PyDebugValue value) { + public void showNumericContainer(@NotNull PyDebugValue value) { PyViewNumericContainerAction.showNumericViewer(myProject, value); } diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index 053a755ae90c..cadd8b1d6db5 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -94,6 +94,8 @@ 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.PySourcePosition; +import com.jetbrains.python.debugger.PyVariableViewSettings; +import com.jetbrains.python.debugger.settings.PyDebuggerSettings; import com.jetbrains.python.remote.PyRemotePathMapper; import com.jetbrains.python.remote.PyRemoteProcessHandlerBase; import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; @@ -223,6 +225,13 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { toolbarActions.add(new ConnectDebuggerAction()); + DefaultActionGroup settings = new DefaultActionGroup("Settings", true); + settings.getTemplatePresentation().setIcon(AllIcons.General.SecondaryGroup); + settings.add(new PyVariableViewSettings.SimplifiedView(null)); + settings.add(new PyVariableViewSettings.AsyncView()); + + toolbarActions.add(settings); + toolbarActions.add(new NewConsoleAction()); return actions; @@ -1111,6 +1120,10 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { mySdk = sdk; myEnvironmentVariables = envs; myEnvironmentVariables.putAll(consoleSettings.getEnvs()); + PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance(); + if (debuggerSettings.isLoadValuesAsync()) { + myEnvironmentVariables.put(PyVariableViewSettings.PYDEVD_LOAD_VALUES_ASYNC, "True"); + } } @Override diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 79210a7ee233..7a2b8be1713d 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -57,10 +57,7 @@ import com.intellij.util.ui.UIUtil; import com.intellij.xdebugger.*; import com.intellij.xdebugger.breakpoints.*; import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; -import com.intellij.xdebugger.frame.XExecutionStack; -import com.intellij.xdebugger.frame.XStackFrame; -import com.intellij.xdebugger.frame.XSuspendContext; -import com.intellij.xdebugger.frame.XValueChildrenList; +import com.intellij.xdebugger.frame.*; import com.intellij.xdebugger.stepping.XSmartStepIntoHandler; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.console.PythonConsoleView; @@ -105,7 +102,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr new ConcurrentHashMap<>(); private final List mySuspendedThreads = Collections.synchronizedList(Lists.newArrayList()); - private final Map myStackFrameCache = Maps.newHashMap(); + private final Map myStackFrameCache = Maps.newConcurrentMap(); + private final Object myFrameCacheObject = new Object(); private final Map myNewVariableValue = Maps.newHashMap(); private boolean myDownloadSources = false; @@ -451,7 +449,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr @NotNull DefaultActionGroup settings) { super.registerAdditionalActions(leftToolbar, topToolbar, settings); settings.add(new WatchReturnValuesAction(this)); - settings.add(new SimplifiedView(this)); + settings.add(new PyVariableViewSettings.SimplifiedView(this)); + settings.add(new PyVariableViewSettings.AsyncView()); } private static class WatchReturnValuesAction extends ToggleAction { @@ -491,38 +490,6 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } } - private static class SimplifiedView extends ToggleAction { - private volatile boolean mySimplifiedView; - private final PyDebugProcess myProcess; - private final String myText; - - public SimplifiedView(@NotNull PyDebugProcess debugProcess) { - super("", "Disables watching classes, functions and modules objects", null); - mySimplifiedView = PyDebuggerSettings.getInstance().isSimplifiedView(); - myProcess = debugProcess; - myText = "Simplified Variables View"; - } - - @Override - public void update(@NotNull final AnActionEvent e) { - super.update(e); - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(true); - presentation.setText(myText); - } - - @Override - public boolean isSelected(AnActionEvent e) { - return mySimplifiedView; - } - - @Override - public void setSelected(AnActionEvent e, boolean hide) { - mySimplifiedView = hide; - PyDebuggerSettings.getInstance().setSimplifiedView(hide); - myProcess.getSession().rebuildViews(); - } - } public void setShowReturnValues(boolean showReturnValues) { myDebugger.setShowReturnValues(showReturnValues); @@ -732,18 +699,67 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } } + @Override + public boolean isCurrentFrameCached() { + try { + synchronized (myFrameCacheObject) { + final PyStackFrame frame = currentFrame(); + return myStackFrameCache.containsKey(frame.getThreadFrameId()); + } + } + catch (PyDebuggerException e) { + LOG.warn(e); + } + return false; + } + @Override @Nullable public XValueChildrenList loadFrame() throws PyDebuggerException { final PyStackFrame frame = currentFrame(); - //do not reload frame every time it is needed, because due to bug in pdb, reloading frame clears all variable changes - if (!myStackFrameCache.containsKey(frame.getThreadFrameId())) { - XValueChildrenList values = myDebugger.loadFrame(frame.getThreadId(), frame.getFrameId()); - myStackFrameCache.put(frame.getThreadFrameId(), values); + synchronized (myFrameCacheObject) { + //do not reload frame every time it is needed, because due to bug in pdb, reloading frame clears all variable changes + if (!myStackFrameCache.containsKey(frame.getThreadFrameId())) { + XValueChildrenList values = myDebugger.loadFrame(frame.getThreadId(), frame.getFrameId()); + myStackFrameCache.put(frame.getThreadFrameId(), values); + } } return applyNewValue(myStackFrameCache.get(frame.getThreadFrameId()), frame.getThreadFrameId()); } + public void loadAsyncVariablesValues(@NotNull final List> pyAsyncValues) { + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + if (isConnected()) { + final PyStackFrame frame = currentFrame(); + XSuspendContext context = getSession().getSuspendContext(); + String threadId = threadIdBeforeResumeOrStep(context); + for (PyThreadInfo suspendedThread : mySuspendedThreads) { + if (threadId == null || threadId.equals(suspendedThread.getId())) { + myDebugger.loadFullVariableValues(frame.getThreadId(), frame.getFrameId(), pyAsyncValues); + break; + } + } + } + } + catch (PyDebuggerException e) { + if (!isConnected()) return; + for (PyAsyncValue asyncValue: pyAsyncValues) { + PyDebugValue value = asyncValue.getDebugValue(); + XValueNode node = value.getLastNode(); + if (node != null && !node.isObsolete()) { + if (e.getMessage().startsWith("Timeout")) { + value.updateNodeValueAfterLoading(node, " ", "Timeout Exceeded"); + } + else { + LOG.error(e); + } + } + } + } + }); + } + private XValueChildrenList applyNewValue(XValueChildrenList pyDebugValues, String threadFrameId) { if (myNewVariableValue.containsKey(threadFrameId)) { PyDebugValue newValue = myNewVariableValue.get(threadFrameId); @@ -767,7 +783,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr @Override public XValueChildrenList loadVariable(final PyDebugValue var) throws PyDebuggerException { final PyStackFrame frame = currentFrame(); - PyDebugValue debugValue = var.setName(var.getFullName()); + PyDebugValue debugValue = new PyDebugValue(var, var.getFullName()); return myDebugger.loadVariable(frame.getThreadId(), frame.getFrameId(), debugValue); } @@ -1183,7 +1199,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } @Override - public void showNumericContainer(PyDebugValue value) { + public void showNumericContainer(@NotNull PyDebugValue value) { PyViewNumericContainerAction.showNumericViewer(getProject(), value); } diff --git a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java index 05960f3929d9..84c7aff0c955 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java @@ -305,7 +305,6 @@ public class PyDebugRunner extends GenericProgramRunner { configureDebugParameters(project, debugParams, pyState, cmd); - configureDebugEnvironment(project, cmd.getEnvironment()); configureDebugConnectionParameters(debugParams, serverLocalPort); @@ -323,6 +322,9 @@ 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"); + } PydevConsoleRunnerFactory.putIPythonEnvFlag(project, environment); diff --git a/python/src/com/jetbrains/python/debugger/PyDebuggerEvaluator.java b/python/src/com/jetbrains/python/debugger/PyDebuggerEvaluator.java index 989dee3fa1a9..9ed48665f827 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebuggerEvaluator.java +++ b/python/src/com/jetbrains/python/debugger/PyDebuggerEvaluator.java @@ -28,8 +28,6 @@ import org.jetbrains.annotations.Nullable; public class PyDebuggerEvaluator extends XDebuggerEvaluator { - private static final PyDebugValue NONE = new PyDebugValue("", "NoneType", null, "None", false, false, false, false, null, null); - private Project myProject; private final PyFrameAccessor myDebugProcess; @@ -43,11 +41,15 @@ public class PyDebuggerEvaluator extends XDebuggerEvaluator { doEvaluate(expression, callback, true); } + private PyDebugValue getNone() { + return new PyDebugValue("", "NoneType", null, "None", false, false, false, false, null, myDebugProcess); + } + private void doEvaluate(final String expr, final XEvaluationCallback callback, final boolean doTrunc) { ApplicationManager.getApplication().executeOnPooledThread(() -> { String expression = expr.trim(); if (expression.isEmpty()) { - callback.evaluated(NONE); + callback.evaluated(getNone()); return; } diff --git a/python/src/com/jetbrains/python/debugger/PyStackFrame.java b/python/src/com/jetbrains/python/debugger/PyStackFrame.java index f0aa6906e552..2c3149b12b1e 100644 --- a/python/src/com/jetbrains/python/debugger/PyStackFrame.java +++ b/python/src/com/jetbrains/python/debugger/PyStackFrame.java @@ -147,10 +147,14 @@ public class PyStackFrame extends XStackFrame { if (node.isObsolete() || !isVariablesViewVisible()) return; ApplicationManager.getApplication().executeOnPooledThread(() -> { try { + boolean cached = myDebugProcess.isCurrentFrameCached(); XValueChildrenList values = myDebugProcess.loadFrame(); if (!node.isObsolete()) { addChildren(node, values); } + if (values != null && !cached) { + PyDebugValue.getAsyncValues(myDebugProcess, values); + } } catch (PyDebuggerException e) { if (!node.isObsolete()) { diff --git a/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java b/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java new file mode 100644 index 000000000000..96c3d250cca2 --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java @@ -0,0 +1,92 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.jetbrains.python.debugger.settings.PyDebuggerSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PyVariableViewSettings { + public static final String PYDEVD_LOAD_VALUES_ASYNC = "PYDEVD_LOAD_VALUES_ASYNC"; + + public static class SimplifiedView extends ToggleAction { + private final PyDebugProcess myProcess; + private final String myText; + private volatile boolean mySimplifiedView; + + public SimplifiedView(@Nullable PyDebugProcess debugProcess) { + super("", "Disables watching classes, functions and modules objects", null); + mySimplifiedView = PyDebuggerSettings.getInstance().isSimplifiedView(); + myProcess = debugProcess; + myText = "Simplified Variables View"; + } + + @Override + public void update(@NotNull final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(true); + presentation.setText(myText); + } + + @Override + public boolean isSelected(AnActionEvent e) { + return mySimplifiedView; + } + + @Override + public void setSelected(AnActionEvent e, boolean hide) { + mySimplifiedView = hide; + PyDebuggerSettings.getInstance().setSimplifiedView(hide); + if (myProcess != null) { + myProcess.getSession().rebuildViews(); + } + } + } + + public static class AsyncView extends ToggleAction { + private final String myText; + private volatile boolean myLazyVariablesEvaluation; + + public AsyncView() { + super("", "Load variable values asynchronously", null); + myLazyVariablesEvaluation = PyDebuggerSettings.getInstance().isLoadValuesAsync(); + myText = "Load Values Asynchronously"; + } + + @Override + public void update(@NotNull final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(true); + presentation.setText(myText); + } + + @Override + public boolean isSelected(AnActionEvent e) { + return myLazyVariablesEvaluation; + } + + @Override + public void setSelected(AnActionEvent e, boolean hide) { + myLazyVariablesEvaluation = hide; + PyDebuggerSettings.getInstance().setLoadValuesAsync(hide); + } + } +} diff --git a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java index 3d4c187e1961..30d29b05f6cb 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java +++ b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.PrioritizedLookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.ex.EditorEx; @@ -81,7 +82,7 @@ public class PyDataViewerPanel extends JPanel { } private void setupChangeListener() { - myFrameAccessor.addFrameListener(() -> UIUtil.invokeLaterIfNeeded(() -> updateModel())); + myFrameAccessor.addFrameListener(() -> ApplicationManager.getApplication().executeOnPooledThread(() -> updateModel())); } private void updateModel() { @@ -91,9 +92,11 @@ public class PyDataViewerPanel extends JPanel { } model.invalidateCache(); updateDebugValue(model); - if (isShowing()) { - model.fireTableDataChanged(); - } + ApplicationManager.getApplication().invokeLater(() -> { + if (isShowing()) { + model.fireTableDataChanged(); + } + }); } private void updateDebugValue(@NotNull AsyncArrayTableModel model) { @@ -153,12 +156,13 @@ public class PyDataViewerPanel extends JPanel { } public void apply(String name) { - myErrorLabel.setVisible(false); - PyDebugValue debugValue = getDebugValue(name); - if (debugValue == null) { - return; - } - apply(debugValue); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + PyDebugValue debugValue = getDebugValue(name); + if (debugValue == null) { + return; + } + ApplicationManager.getApplication().invokeLater(() -> apply(debugValue)); + }); } public void apply(@NotNull PyDebugValue debugValue) { @@ -169,13 +173,15 @@ public class PyDataViewerPanel extends JPanel { setError(type + " is not supported"); return; } - try { - ArrayChunk arrayChunk = debugValue.getFrameAccessor().getArrayItems(debugValue, 0, 0, -1, -1, getFormat()); - updateUI(arrayChunk, debugValue, strategy); - } - catch (PyDebuggerException e) { - LOG.error(e); - } + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + ArrayChunk arrayChunk = debugValue.getFrameAccessor().getArrayItems(debugValue, 0, 0, -1, -1, getFormat()); + ApplicationManager.getApplication().invokeLater(() -> updateUI(arrayChunk, debugValue, strategy)); + } + catch (PyDebuggerException e) { + LOG.error(e); + } + }); } public void resize(boolean autoResize) { diff --git a/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java b/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java index cb7e8646c32d..e3b1020ed991 100644 --- a/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java +++ b/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java @@ -40,6 +40,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; public PyDebuggerSettings() { super("python"); @@ -62,6 +63,14 @@ public class PyDebuggerSettings extends XDebuggerSettings im mySimplifiedView = simplifiedView; } + public boolean isLoadValuesAsync() { + return myLoadValuesAsync; + } + + public void setLoadValuesAsync(boolean loadValuesAsync) { + myLoadValuesAsync = loadValuesAsync; + } + public static PyDebuggerSettings getInstance() { return getInstance(PyDebuggerSettings.class); } diff --git a/python/testData/debug/test_async_eval.py b/python/testData/debug/test_async_eval.py new file mode 100644 index 000000000000..04a7d4aca99b --- /dev/null +++ b/python/testData/debug/test_async_eval.py @@ -0,0 +1,15 @@ +import time + + +class Foo(object): + def __init__(self, name): + self.name = name + + def __repr__(self): + time.sleep(1) + return self.name + + +f = Foo("foo") +l = [Foo("list"), Foo("list")] +a = 1 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index 769e123163eb..38afc99bb67b 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -1326,6 +1326,30 @@ public class PythonDebuggerTest extends PyEnvTestCase { }); } + @Test + public void testLoadValuesAsync() { + runPythonTest(new PyDebuggerTask("/debug", "test_async_eval.py") { + @Override + public void before() { + toggleBreakpoint(getFilePath(getScriptName()), 14); + } + + @Override + public void testing() throws Exception { + waitForPause(); + List frameVariables = loadFrame(); + assertTrue(findDebugValueByName(frameVariables, "f").isLoadValueAsync()); + String result = computeValueAsync(frameVariables, "f"); + assertEquals("foo", result); + + List listChildren = loadChildren(frameVariables, "l"); + assertTrue(findDebugValueByName(frameVariables, "l").isLoadValueAsync()); + result = computeValueAsync(listChildren, "0"); + assertEquals("list", result); + } + }); + } + //TODO: That doesn't work now: case from test_continuation.py and test_continuation2.py are treated differently by interpreter // (first line is executed in first case and last line in second) diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java index 2257b97a7130..1eed80048592 100644 --- a/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java +++ b/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java @@ -45,6 +45,8 @@ import org.junit.Assert; import javax.swing.*; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.Semaphore; @@ -165,6 +167,56 @@ public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask { return result.first; } + protected List loadChildren(List debugValues, String name) throws PyDebuggerException { + PyDebugValue var = findDebugValueByName(debugValues, name); + return convertToList(myDebugProcess.loadVariable(var)); + } + + protected List loadFrame() throws PyDebuggerException { + return convertToList(myDebugProcess.loadFrame()); + } + + protected String computeValueAsync(List debugValues, String name) throws PyDebuggerException { + final PyDebugValue debugValue = findDebugValueByName(debugValues, name); + assert debugValue != null; + Semaphore variableSemaphore = new Semaphore(0); + ArrayList> valuesForEvaluation = new ArrayList<>(); + valuesForEvaluation.add(new PyFrameAccessor.PyAsyncValue<>(debugValue, new PyDebugCallback() { + @Override + public void ok(String value) { + debugValue.setValue(value); + variableSemaphore.release(); + } + + @Override + public void error(PyDebuggerException exception) { + variableSemaphore.release(); + } + })); + myDebugProcess.loadAsyncVariablesValues(valuesForEvaluation); + XDebuggerTestUtil.waitFor(variableSemaphore, NORMAL_TIMEOUT); + return debugValue.getValue(); + } + + public static List convertToList(XValueChildrenList childrenList) { + List values = new ArrayList<>(); + for (int i = 0; i < childrenList.size(); i++) { + PyDebugValue value = (PyDebugValue)childrenList.getValue(i); + values.add(value); + } + return values; + } + + @Nullable + public static PyDebugValue findDebugValueByName(@NotNull List debugValues, @NotNull String name) { + for (PyDebugValue val : debugValues) { + if (val.getName().equals(name)) { + return val; + } + } + return null; + } + @NotNull protected String output() { if (mySession != null && mySession.getConsoleView() != null) {