mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Add ability to load variables in the Variables Pane asynchronously (PY-12987)
If in the frame there is at least one very long or slow variable, the frame's variable aren't shown. In order to avoid such situations we add an option to load variables asynchronously. At first debugger loads only names of the variables, and after that it sends additional commands and evaluates values of the variables in a separate threads.
This commit is contained in:
@@ -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>"
|
||||
xml += pydevd_xml.frame_vars_to_xml(self.get_namespace(), hidden_ns)
|
||||
xml += "</xml>"
|
||||
xml.write("<xml>")
|
||||
xml.write(pydevd_xml.frame_vars_to_xml(self.get_namespace(), hidden_ns))
|
||||
xml.write("</xml>")
|
||||
|
||||
return xml
|
||||
return xml.getvalue()
|
||||
|
||||
def getVariable(self, attributes):
|
||||
xml = "<xml>"
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
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>"
|
||||
xml.write("</xml>")
|
||||
|
||||
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>"
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace())
|
||||
xml.write(pydevd_vars.var_to_xml(result, expression))
|
||||
xml.write("</xml>")
|
||||
return xml.getvalue()
|
||||
|
||||
xml += pydevd_vars.var_to_xml(result, expression)
|
||||
|
||||
xml += "</xml>"
|
||||
|
||||
return xml
|
||||
def loadFullValue(self, expressions):
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
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("</xml>")
|
||||
return xml.getvalue()
|
||||
|
||||
def changeVariable(self, attr, value):
|
||||
def do_change_variable():
|
||||
|
||||
@@ -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>"
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
_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 += "</xml>"
|
||||
cmd = dbg.cmd_factory.make_get_variable_message(self.sequence, xml)
|
||||
xml.write("</xml>")
|
||||
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("<xml>")
|
||||
for (var_obj, name) in self.var_objs:
|
||||
xml.write(pydevd_xml.var_to_xml(var_obj, name, evaluate_full_value=True))
|
||||
xml.write("</xml>")
|
||||
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
|
||||
#=======================================================================================================================
|
||||
|
||||
@@ -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():
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
<var name="var_name" scope="local" type="type" value="value"/>
|
||||
"""
|
||||
@@ -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__), '<Too big to print. Len: %s>' % (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 <type 'int'>)
|
||||
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__), '<Too big to print. Len: %s>' % (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 <type 'int'>)
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String> createDebugValueCallback() {
|
||||
return new PyDebugCallback<String>() {
|
||||
@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<PyFrameAccessor.PyAsyncValue<String>> getAsyncValuesFromChildren(@NotNull XValueChildrenList childrenList) {
|
||||
List<PyFrameAccessor.PyAsyncValue<String>> 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<PyFrameAccessor.PyAsyncValue<String>> 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*'(?<TYPE>.*?)'>");
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -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<PyAsyncValue<String>> pyAsyncValues) {}
|
||||
|
||||
default boolean isCurrentFrameCached() {
|
||||
return false;
|
||||
}
|
||||
|
||||
class PyAsyncValue<T> {
|
||||
private final @NotNull PyDebugValue myDebugValue;
|
||||
private final @NotNull PyDebugCallback<T> myCallback;
|
||||
|
||||
public PyAsyncValue(@NotNull PyDebugValue debugValue, @NotNull PyDebugCallback<T> callback) {
|
||||
myDebugValue = debugValue;
|
||||
myCallback = callback;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyDebugValue getDebugValue() {
|
||||
return myDebugValue;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyDebugCallback<T> getCallback() {
|
||||
return myCallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ public abstract class AbstractCommand<T> {
|
||||
|
||||
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<T> {
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+7
@@ -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<PyFrameAccessor.PyAsyncValue<String>> vars) throws PyDebuggerException {
|
||||
debugger(threadId).loadFullVariableValues(threadId, frameId, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String loadSource(String path) {
|
||||
return myMainDebugger.loadSource(path);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<PyDebugValue> 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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PyFrameAccessor.PyAsyncValue<String>> myVars;
|
||||
|
||||
public LoadFullValueCommand(final @NotNull RemoteDebugger debugger,
|
||||
final @NotNull String threadId,
|
||||
final @NotNull String frameId,
|
||||
final @NotNull List<PyFrameAccessor.PyAsyncValue<String>> 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<PyDebugValue> 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<String> var : myVars) {
|
||||
PyDebugValue debugValue = var.getDebugValue();
|
||||
payload.add("FRAME").add(buildPayloadForVar(debugValue)).add(NEXT_VALUE_SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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<PyFrameAccessor.PyAsyncValue<String>> vars) throws PyDebuggerException {
|
||||
debugger(threadId).loadFullVariableValues(threadId, frameId, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String loadSource(String path) {
|
||||
return myMainDebugger.loadSource(path);
|
||||
|
||||
@@ -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<PyFrameAccessor.PyAsyncValue<String>> vars)
|
||||
throws PyDebuggerException;
|
||||
|
||||
@Nullable
|
||||
String loadSource(String path);
|
||||
|
||||
|
||||
@@ -205,6 +205,13 @@ public class RemoteDebugger implements ProcessDebugger {
|
||||
return command.getNewValue();
|
||||
}
|
||||
|
||||
public void loadFullVariableValues(@NotNull String threadId,
|
||||
@NotNull String frameId,
|
||||
@NotNull List<PyFrameAccessor.PyAsyncValue<String>> vars) throws PyDebuggerException {
|
||||
final LoadFullValueCommand command = new LoadFullValueCommand(this, threadId, frameId, vars);
|
||||
command.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String loadSource(String path) {
|
||||
|
||||
@@ -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<Boolean, String> result = ProtocolParser.parseSetNextStatementCommand(response.getPayload());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<PyAsyncValue<String>> pyAsyncValues) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
if (myClient != null) {
|
||||
try {
|
||||
List<String> evaluationExpressions = new ArrayList<>();
|
||||
for (PyAsyncValue<String> asyncValue : pyAsyncValues) {
|
||||
evaluationExpressions.add(asyncValue.getDebugValue().getEvaluationExpression());
|
||||
}
|
||||
Object ret = myClient.execute(LOAD_FULL_VALUE, new Object[]{evaluationExpressions.toArray()});
|
||||
|
||||
if (ret instanceof String) {
|
||||
List<PyDebugValue> 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<String> 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<PyDebugValue> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<PyThreadInfo> mySuspendedThreads = Collections.synchronizedList(Lists.<PyThreadInfo>newArrayList());
|
||||
private final Map<String, XValueChildrenList> myStackFrameCache = Maps.newHashMap();
|
||||
private final Map<String, XValueChildrenList> myStackFrameCache = Maps.newConcurrentMap();
|
||||
private final Object myFrameCacheObject = new Object();
|
||||
private final Map<String, PyDebugValue> 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<PyAsyncValue<String>> 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<String> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -40,6 +40,7 @@ public class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettings> 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<PyDebuggerSettings> im
|
||||
mySimplifiedView = simplifiedView;
|
||||
}
|
||||
|
||||
public boolean isLoadValuesAsync() {
|
||||
return myLoadValuesAsync;
|
||||
}
|
||||
|
||||
public void setLoadValuesAsync(boolean loadValuesAsync) {
|
||||
myLoadValuesAsync = loadValuesAsync;
|
||||
}
|
||||
|
||||
public static PyDebuggerSettings getInstance() {
|
||||
return getInstance(PyDebuggerSettings.class);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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<PyDebugValue> frameVariables = loadFrame();
|
||||
assertTrue(findDebugValueByName(frameVariables, "f").isLoadValueAsync());
|
||||
String result = computeValueAsync(frameVariables, "f");
|
||||
assertEquals("foo", result);
|
||||
|
||||
List<PyDebugValue> 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)
|
||||
|
||||
|
||||
@@ -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<PyDebugValue> loadChildren(List<PyDebugValue> debugValues, String name) throws PyDebuggerException {
|
||||
PyDebugValue var = findDebugValueByName(debugValues, name);
|
||||
return convertToList(myDebugProcess.loadVariable(var));
|
||||
}
|
||||
|
||||
protected List<PyDebugValue> loadFrame() throws PyDebuggerException {
|
||||
return convertToList(myDebugProcess.loadFrame());
|
||||
}
|
||||
|
||||
protected String computeValueAsync(List<PyDebugValue> debugValues, String name) throws PyDebuggerException {
|
||||
final PyDebugValue debugValue = findDebugValueByName(debugValues, name);
|
||||
assert debugValue != null;
|
||||
Semaphore variableSemaphore = new Semaphore(0);
|
||||
ArrayList<PyFrameAccessor.PyAsyncValue<String>> valuesForEvaluation = new ArrayList<>();
|
||||
valuesForEvaluation.add(new PyFrameAccessor.PyAsyncValue<>(debugValue, new PyDebugCallback<String>() {
|
||||
@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<PyDebugValue> convertToList(XValueChildrenList childrenList) {
|
||||
List<PyDebugValue> 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<PyDebugValue> 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) {
|
||||
|
||||
Reference in New Issue
Block a user