diff -r 17fb004af51d -r 890dfe038613 eric7/DebugClients/Python/DebugClientBase.py --- a/eric7/DebugClients/Python/DebugClientBase.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/DebugClients/Python/DebugClientBase.py Sun Aug 29 19:19:31 2021 +0200 @@ -27,7 +27,7 @@ import DebugVariables from DebugBase import setRecursionLimit, printerr # __IGNORE_WARNING__ from AsyncFile import AsyncFile, AsyncPendingWrite -from DebugConfig import ConfigQtNames, SpecialAttributes +from DebugConfig import SpecialAttributes, NonExpandableTypes from FlexCompleter import Completer from DebugUtilities import prepareJsonCommand from BreakpointWatch import Breakpoint, Watch @@ -126,11 +126,17 @@ """ clientCapabilities = DebugClientCapabilities.HasAll - # keep these in sync with VariablesViewer.VariableItem.Indicators - Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING_M613__ - arrayTypes = { - 'list', 'tuple', 'dict', 'set', 'frozenset', "class 'dict_items'", - "class 'dict_keys'", "class 'dict_values'" + Type2Indicators = { + # Python types + 'list': '[]', + 'tuple': '()', + 'dict': '{:}', # __IGNORE_WARNING_M613__ + 'set': '{}', # __IGNORE_WARNING_M613__ + 'frozenset': '{}', # __IGNORE_WARNING_M613__ + 'numpy.ndarray': '[ndarray]', # __IGNORE_WARNING_M613__ + 'collections.abc.ItemsView': '[]', + 'collections.abc.KeysView': '[]', + 'collections.abc.ValuesView': '[]', } def __init__(self): @@ -151,9 +157,9 @@ # The list of complete lines to execute. self.buffer = '' - # The list of regexp objects to filter variables against - self.globalsFilterObjects = [] - self.localsFilterObjects = [] + # The precompiled regexp to filter variables against + self.globalsFilterObjects = None + self.localsFilterObjects = None self._fncache = {} self.dircache = [] @@ -1520,8 +1526,11 @@ else: varDict = f.f_locals + # Update known types list + DebugVariables.updateTypeMap() + varlist = [] if scope < 0 else self.__formatVariablesList( - varDict, scope, filterList) + varDict.items(), scope, filterList) self.sendJsonCommand("ResponseVariables", { "scope": scope, @@ -1565,16 +1574,19 @@ varlist = [] + # fast path if variable was looked up before (see elif) if scope != -1 and str(var) in self.resolverCache[scope]: varGen = self.resolverCache[scope][str(var)] idx, varDict = next(varGen) - var.insert(0, idx) - varlist = self.__formatVariablesList(varDict, scope, filterList) + if idx != -2: # more elements available + var.insert(0, idx) + varlist = self.__formatVariablesList( + varDict, scope, filterList) elif scope != -1: variable = varDict # Lookup the wanted attribute for attribute in var: - _, _, resolver = DebugVariables.getType(variable) + resolver = DebugVariables.getResolver(variable) if resolver: variable = resolver.resolve(variable, attribute) if variable is None: @@ -1586,22 +1598,16 @@ idx = -3 # Requested variable doesn't exist anymore # If found, get the details of attribute if variable is not None: - typeName, typeStr, resolver = DebugVariables.getType(variable) + resolver = DebugVariables.getResolver(variable) if resolver: - varGen = resolver.getDictionary(variable) + varGen = resolver.getVariableList(variable) + # cache for next lookup self.resolverCache[scope][str(var)] = varGen idx, varDict = next(varGen) - varlist = self.__formatVariablesList( - varDict, scope, filterList) - else: - # Gently handle exception which could occur as special - # cases, e.g. already deleted C++ objects, str conversion.. - try: - varlist = self.__formatQtVariable(variable, typeName) - except Exception: - varlist = [] - idx = -1 + if idx != -2: # more elements available + varlist = self.__formatVariablesList( + varDict, scope, filterList) var.insert(0, idx) @@ -1611,169 +1617,7 @@ "variables": varlist, }) - def __extractIndicators(self, var): - """ - Private method to extract the indicator string from a variable text. - - @param var variable text - @type str - @return tuple containing the variable text without indicators and the - indicator string - @rtype tuple of two str - """ - for indicator in DebugClientBase.Indicators: - if var.endswith(indicator): - return var[:-len(indicator)], indicator - - return var, "" - - def __formatQtVariable(self, value, qttype): - """ - Private method to produce a formatted output of a simple Qt5/Qt6 type. - - @param value variable to be formatted - @param qttype type of the Qt variable to be formatted (string) - @return A tuple consisting of a list of formatted variables. Each - variable entry is a tuple of three elements, the variable name, - its type and value. - """ - varlist = [] - if qttype == 'QChar': - varlist.append( - ("", "QChar", "{0}".format(chr(value.unicode())))) - varlist.append(("", "int", "{0:d}".format(value.unicode()))) - elif qttype == 'QByteArray': - varlist.append( - ("bytes", "QByteArray", "{0}".format(bytes(value))[2:-1])) - varlist.append( - ("hex", "QByteArray", "{0}".format(value.toHex())[2:-1])) - varlist.append( - ("base64", "QByteArray", "{0}".format(value.toBase64())[2:-1])) - varlist.append(("percent encoding", "QByteArray", - "{0}".format(value.toPercentEncoding())[2:-1])) - elif qttype == 'QString': - varlist.append(("", "QString", "{0}".format(value))) - elif qttype == 'QStringList': - for i in range(value.count()): - varlist.append( - ("{0:d}".format(i), "QString", "{0}".format(value[i]))) - elif qttype == 'QPoint': - varlist.append(("x", "int", "{0:d}".format(value.x()))) - varlist.append(("y", "int", "{0:d}".format(value.y()))) - elif qttype == 'QPointF': - varlist.append(("x", "float", "{0:g}".format(value.x()))) - varlist.append(("y", "float", "{0:g}".format(value.y()))) - elif qttype == 'QRect': - varlist.append(("x", "int", "{0:d}".format(value.x()))) - varlist.append(("y", "int", "{0:d}".format(value.y()))) - varlist.append(("width", "int", "{0:d}".format(value.width()))) - varlist.append(("height", "int", "{0:d}".format(value.height()))) - elif qttype == 'QRectF': - varlist.append(("x", "float", "{0:g}".format(value.x()))) - varlist.append(("y", "float", "{0:g}".format(value.y()))) - varlist.append(("width", "float", "{0:g}".format(value.width()))) - varlist.append(("height", "float", "{0:g}".format(value.height()))) - elif qttype == 'QSize': - varlist.append(("width", "int", "{0:d}".format(value.width()))) - varlist.append(("height", "int", "{0:d}".format(value.height()))) - elif qttype == 'QSizeF': - varlist.append(("width", "float", "{0:g}".format(value.width()))) - varlist.append(("height", "float", "{0:g}".format(value.height()))) - elif qttype == 'QColor': - varlist.append(("name", "str", "{0}".format(value.name()))) - r, g, b, a = value.getRgb() - varlist.append( - ("rgba", "int", - "{0:d}, {1:d}, {2:d}, {3:d}".format(r, g, b, a))) - h, s, v, a = value.getHsv() - varlist.append( - ("hsva", "int", - "{0:d}, {1:d}, {2:d}, {3:d}".format(h, s, v, a))) - c, m, y, k, a = value.getCmyk() - varlist.append( - ("cmyka", "int", - "{0:d}, {1:d}, {2:d}, {3:d}, {4:d}".format(c, m, y, k, a))) - elif qttype == 'QDate': - varlist.append(("", "QDate", "{0}".format(value.toString()))) - elif qttype == 'QTime': - varlist.append(("", "QTime", "{0}".format(value.toString()))) - elif qttype == 'QDateTime': - varlist.append(("", "QDateTime", "{0}".format(value.toString()))) - elif qttype == 'QDir': - varlist.append(("path", "str", "{0}".format(value.path()))) - varlist.append(("absolutePath", "str", - "{0}".format(value.absolutePath()))) - varlist.append(("canonicalPath", "str", - "{0}".format(value.canonicalPath()))) - elif qttype == 'QFile': - varlist.append(("fileName", "str", "{0}".format(value.fileName()))) - elif qttype == 'QFont': - varlist.append(("family", "str", "{0}".format(value.family()))) - varlist.append( - ("pointSize", "int", "{0:d}".format(value.pointSize()))) - varlist.append(("weight", "int", "{0:d}".format(value.weight()))) - varlist.append(("bold", "bool", "{0}".format(value.bold()))) - varlist.append(("italic", "bool", "{0}".format(value.italic()))) - elif qttype == 'QUrl': - varlist.append(("url", "str", "{0}".format(value.toString()))) - varlist.append(("scheme", "str", "{0}".format(value.scheme()))) - varlist.append(("user", "str", "{0}".format(value.userName()))) - varlist.append(("password", "str", "{0}".format(value.password()))) - varlist.append(("host", "str", "{0}".format(value.host()))) - varlist.append(("port", "int", "{0:d}".format(value.port()))) - varlist.append(("path", "str", "{0}".format(value.path()))) - elif qttype == 'QModelIndex': - varlist.append(("valid", "bool", "{0}".format(value.isValid()))) - if value.isValid(): - varlist.append(("row", "int", "{0}".format(value.row()))) - varlist.append(("column", "int", "{0}".format(value.column()))) - varlist.append( - ("internalId", "int", "{0}".format(value.internalId()))) - varlist.append(("internalPointer", "void *", - "{0}".format(value.internalPointer()))) - elif qttype in ('QRegExp', "QRegularExpression"): - varlist.append(("pattern", "str", "{0}".format(value.pattern()))) - - # GUI stuff - elif qttype == 'QAction': - varlist.append(("name", "str", "{0}".format(value.objectName()))) - varlist.append(("text", "str", "{0}".format(value.text()))) - varlist.append( - ("icon text", "str", "{0}".format(value.iconText()))) - varlist.append(("tooltip", "str", "{0}".format(value.toolTip()))) - varlist.append( - ("whatsthis", "str", "{0}".format(value.whatsThis()))) - varlist.append( - ("shortcut", "str", - "{0}".format(value.shortcut().toString()))) - elif qttype == 'QKeySequence': - varlist.append(("value", "", "{0}".format(value.toString()))) - - # XML stuff - elif qttype == 'QDomAttr': - varlist.append(("name", "str", "{0}".format(value.name()))) - varlist.append(("value", "str", "{0}".format(value.value()))) - elif qttype in ('QDomCharacterData', 'QDomComment', 'QDomText'): - varlist.append(("data", "str", "{0}".format(value.data()))) - elif qttype == 'QDomDocument': - varlist.append(("text", "str", "{0}".format(value.toString()))) - elif qttype == 'QDomElement': - varlist.append(("tagName", "str", "{0}".format(value.tagName()))) - varlist.append(("text", "str", "{0}".format(value.text()))) - - # Networking stuff - elif qttype == 'QHostAddress': - varlist.append( - ("address", "QHostAddress", "{0}".format(value.toString()))) - - # PySide specific - elif qttype == 'EnumType': # Not in PyQt possible - for key, value in value.values.items(): - varlist.append((key, qttype, "{0}".format(int(value)))) - - return varlist - - def __formatVariablesList(self, dict_, scope, filterList=None): + def __formatVariablesList(self, variables, scope, filterList=None): """ Private method to produce a formated variables list. @@ -1799,7 +1643,7 @@ its type and value. @rtype list of tuple of (str, str, str) """ - filterList = [] if filterList is None else filterList[:] + filterList = set(filterList or []) varlist = [] patternFilterObjects = ( @@ -1807,27 +1651,26 @@ if scope else self.localsFilterObjects ) - if type(dict_) == dict: - dict_ = dict_.items() - for key, value in dict_: - # no more elements available - if key == -2: - break + for variabel in variables: + valtype = None + rvalue = None + try: + key, value = variabel + except ValueError: + # Special case for some Qt variables, where the real type is + # overwritten + key, valtype, rvalue = variabel # filter based on the filter pattern - matched = False - for pat in patternFilterObjects: - if pat.match(str(key)): - matched = True - break - if matched: + if patternFilterObjects and patternFilterObjects.match(str(key)): continue # filter hidden attributes (filter #0) if '__' in filterList and str(key)[:2] == '__': continue + hasChildren = False # special handling for '__builtins__' (it's way too big) if key == '__builtins__': rvalue = '<module builtins (built-in)>' @@ -1841,67 +1684,92 @@ "builtin_function_or_method" in filterList) ): continue - else: - isQt = False + elif valtype is None: # valtypestr, e.g. class 'PyQt6.QtCore.QPoint' - valtypestr = str(type(value))[1:-1] + valtypestr = str(type(value)) _, valtype = valtypestr.split(' ', 1) - # valtype, e.g. PyQt6.QtCore.QPoint - valtype = valtype[1:-1] + # valtype is the real type, e.g. PyQt6.QtCore.QPoint + # valtype_filter is used for filtering, where the base class is + # also important + valtype = valtype_filter = valtype[1:-2] # Strip 'instance' to be equal with Python 3 if valtype == "instancemethod": - valtype = "method" - elif valtype in ("type", "classobj"): - valtype = "class" + valtype = valtype_filter = "method" + elif isinstance(value, type): + valtype_filter = "class" + if valtype == "type": + valtype = "class" elif valtype == "method-wrapper": - valtype = "builtin_function_or_method" + valtype = valtype_filter = "builtin_function_or_method" - # valtypename, e.g. QPoint - valtypename = type(value).__name__ + # Don't process variables which types are on filter list if ( - valtype in filterList or - (valtype in ("sip.enumtype", "sip.wrappertype") and + valtype_filter in filterList or + (valtype_filter in ("sip.enumtype", "sip.wrappertype") and 'class' in filterList) or - (valtype in ( + (valtype_filter in ( "sip.methoddescriptor", "method_descriptor") and 'method' in filterList) or - (valtype in ("numpy.ndarray", "array.array") and + (valtype_filter in ("numpy.ndarray", "array.array") and 'list' in filterList) or - (valtypename == "MultiValueDict" and + (valtype_filter == "django.MultiValueDict" and 'dict' in filterList) or 'instance' in filterList ): continue - - isQt = valtype.startswith(ConfigQtNames) + + length = -2 + indicator = '' + + if valtype == 'str': + rvalue = repr(value) + length = len(rvalue) + elif valtype in NonExpandableTypes: + rvalue = repr(value) + + if rvalue is not None: + varlist.append( + (key, indicator, valtype, hasChildren, length, rvalue) + ) + continue + + try: + for dtype in DebugVariables._ArrayTypes: + if isinstance(value, dtype): + try: + length = len(value) + except TypeError: + length = -1 # Uninitialized array + + dtype = str(dtype)[8:-2] + # Standard array type indicators + indicator = self.Type2Indicators.get(dtype, '') + + # Special handling of some array types + if valtype == 'array.array': + indicator = '[<{0}>]'.format(value.typecode) + elif valtype == 'collections.defaultdict': + if value.default_factory is None: + def_factory = "None" + else: + def_factory = value.default_factory.__name__ + indicator = '{{:<{0}>}}'.format(def_factory) + elif valtype == "numpy.ndarray" and length > -1: + length = "x".join(str(x) for x in value.shape) + elif valtype.endswith(".MultiValueDict"): + indicator = "{:}" + valtype = "django.MultiValueDict" # shortened type + break + else: + rvalue = repr(value) - try: - if valtype in self.arrayTypes: - rvalue = "{0:d}".format(len(value)) - elif valtype == 'array.array': - rvalue = "{0:d}|{1}".format( - len(value), value.typecode) - elif valtype == 'collections.defaultdict': - if value.default_factory is None: - factoryName = "None" - else: - factoryName = value.default_factory.__name__ - rvalue = "{0:d}|{1}".format(len(value), factoryName) - elif valtype == "numpy.ndarray": - rvalue = "x".join(str(x) for x in value.shape) - elif valtypename == "MultiValueDict": - rvalue = "{0:d}".format(len(value.keys())) - valtype = "django.MultiValueDict" # shortened type - else: - rvalue = repr(value) - if valtype.startswith('class') and rvalue[0] in '{([': - rvalue = "" - elif (isQt and rvalue.startswith("<class '")): - rvalue = rvalue[8:-2] - except Exception: - rvalue = '' - - varlist.append((key, valtype, rvalue)) + hasChildren = True + except Exception: + rvalue = '' + + varlist.append( + (key, indicator, valtype, hasChildren, length, rvalue) + ) return varlist @@ -1913,13 +1781,18 @@ variables (int) @param filterString string of filter patterns separated by ';' """ - patternFilterObjects = [] - for pattern in filterString.split(';'): - patternFilterObjects.append(re.compile('^{0}$'.format(pattern))) + patternFilterObjects = None + if filterString.strip(): + pattern = filterString.replace(';', '|') + try: + patternFilterObjects = re.compile(pattern) + except re.error: + pass + if scope: - self.globalsFilterObjects = patternFilterObjects[:] + self.globalsFilterObjects = patternFilterObjects else: - self.localsFilterObjects = patternFilterObjects[:] + self.localsFilterObjects = patternFilterObjects def __completionList(self, text): """