eric7/DebugClients/Python/DebugClientBase.py

branch
eric7
changeset 8568
890dfe038613
parent 8549
15eca21fd968
child 8573
77845f40ebfe
--- 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):
         """

eric ide

mercurial