Fri, 16 Sep 2016 19:28:39 +0200
Continued improving the variable dumping of the debugger backends.
--- a/DebugClients/Python2/DebugBase.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python2/DebugBase.py Fri Sep 16 19:28:39 2016 +0200 @@ -886,8 +886,8 @@ 'DebugClientCapabilities.py', 'DebugClientThreads.py', 'DebugConfig.py', 'DebugThread.py', - 'DebugUtilities.py', 'FlexCompleter.py', - 'PyProfile.py'] or \ + 'DebugUtilities.py', 'DebugVariables.py', + 'FlexCompleter.py', 'PyProfile.py'] or \ os.path.dirname(fn).endswith("coverage"): return True
--- a/DebugClients/Python2/DebugClientBase.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python2/DebugClientBase.py Fri Sep 16 19:28:39 2016 +0200 @@ -27,6 +27,7 @@ from DebugConfig import ConfigVarTypeStrings from FlexCompleter import Completer from DebugUtilities import prepareJsonCommand +import DebugVariables DebugClientInstance = None @@ -164,6 +165,9 @@ """ clientCapabilities = DebugClientCapabilities.HasAll + # keep these in sync with VariablesViewer.VariableItem.Indicators + Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING__ + def __init__(self): """ Constructor @@ -1431,217 +1435,25 @@ varlist = [] if scope != -1: - # search the correct dictionary - i = 0 - rvar = var[:] - dictkeys = None - obj = None - isDict = False - formatSequences = False - access = "" - oaccess = "" - odict = dict - - qtVariable = False - qvar = None - qvtype = "" - - while i < len(var): - if len(dict): - udict = dict - ndict = {} - # this has to be in line with VariablesViewer.indicators - if var[i][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__ - if i + 1 == len(var): - if var[i][:-2] == '...': - dictkeys = [var[i - 1]] - else: - dictkeys = [var[i][:-2]] - formatSequences = True - if not access and not oaccess: - if var[i][:-2] == '...': - access = '["%s"]' % var[i - 1] - dict = odict - else: - access = '["%s"]' % var[i][:-2] - else: - if var[i][:-2] == '...': - if oaccess: - access = oaccess - else: - access = '%s[%s]' % (access, var[i - 1]) - dict = odict - else: - if oaccess: - access = '%s[%s]' % (oaccess, var[i][:-2]) - oaccess = '' - else: - access = '%s[%s]' % (access, var[i][:-2]) - if var[i][-2:] == "{}": # __IGNORE_WARNING__ - isDict = True - break - else: - if not access: - if var[i][:-2] == '...': - access = '["%s"]' % var[i - 1] - dict = odict - else: - access = '["%s"]' % var[i][:-2] - else: - if var[i][:-2] == '...': - access = '%s[%s]' % (access, var[i - 1]) - dict = odict - else: - if oaccess: - access = '%s[%s]' % (oaccess, var[i][:-2]) - oaccess = '' - else: - access = '%s[%s]' % (access, var[i][:-2]) + variable = dict + for attribute in var: + attribute = self.__extractIndicators(attribute)[0] + typeObject, typeName, typeStr, resolver = \ + DebugVariables.getType(variable) + if resolver: + variable = resolver.resolve(variable, attribute) else: - if access: - if oaccess: - access = '%s[%s]' % (oaccess, var[i]) - else: - access = '%s[%s]' % (access, var[i]) - if var[i - 1][:-2] == '...': - oaccess = access - else: - oaccess = '' - try: - exec 'mdict = dict%s.__dict__' % access - ndict.update(mdict) # __IGNORE_WARNING__ - exec 'obj = dict%s' % access - if "PyQt4." in str(type(obj)) or \ - "PyQt5." in str(type(obj)): - qtVariable = True - qvar = obj - qvtype = ("%s" % type(qvar))[1:-1]\ - .split()[1][1:-1] - except Exception: - pass - try: - exec 'mcdict = dict%s.__class__.__dict__' % access - ndict.update(mcdict) # __IGNORE_WARNING__ - if mdict and "sipThis" not in mdict.keys(): # __IGNORE_WARNING__ - del rvar[0:2] - access = "" - except Exception: - pass - try: - cdict = {} - exec 'slv = dict%s.__slots__' % access - for v in slv: # __IGNORE_WARNING__ - try: - exec 'cdict[v] = dict%s.%s' % (access, v) - except Exception: - pass - ndict.update(cdict) - exec 'obj = dict%s' % access - access = "" - if "PyQt4." in str(type(obj)) or \ - "PyQt5." in str(type(obj)): - qtVariable = True - qvar = obj - qvtype = ("%s" % type(qvar))[1:-1]\ - .split()[1][1:-1] - except Exception: - pass - else: - try: - ndict.update(dict[var[i]].__dict__) - ndict.update(dict[var[i]].__class__.__dict__) - del rvar[0] - obj = dict[var[i]] - if "PyQt4." in str(type(obj)) or \ - "PyQt5." in str(type(obj)): - qtVariable = True - qvar = obj - qvtype = ("%s" % type(qvar))[1:-1]\ - .split()[1][1:-1] - except Exception: - pass - try: - cdict = {} - slv = dict[var[i]].__slots__ - for v in slv: - try: - exec 'cdict[v] = dict[var[i]].%s' % v - except Exception: - pass - ndict.update(cdict) - obj = dict[var[i]] - if "PyQt4." in str(type(obj)) or \ - "PyQt5." in str(type(obj)): - qtVariable = True - qvar = obj - qvtype = ("%s" % type(qvar))[1:-1]\ - .split()[1][1:-1] - except Exception: - pass - odict = dict - dict = ndict - i += 1 - - if qtVariable: - vlist = self.__formatQtVariable(qvar, qvtype) - elif ("sipThis" in dict.keys() and len(dict) == 1) or \ - (len(dict) == 0 and len(udict) > 0): - if access: - exec 'qvar = udict%s' % access - # this has to be in line with VariablesViewer.indicators - elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__ - exec 'qvar = udict["%s"][%s]' % (rvar[0][:-2], rvar[1]) - else: - qvar = udict[var[-1]] - qvtype = ("%s" % type(qvar))[1:-1].split()[1][1:-1] - if qvtype.startswith(("PyQt4", "PyQt5")): - vlist = self.__formatQtVariable(qvar, qvtype) - else: - vlist = [] - else: - qtVariable = False - if len(dict) == 0 and len(udict) > 0: - if access: - exec 'qvar = udict%s' % access - # this has to be in line with VariablesViewer.indicators - elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__ - exec 'qvar = udict["%s"][%s]' % (rvar[0][:-2], rvar[1]) - else: - qvar = udict[var[-1]] - qvtype = ("%s" % type(qvar))[1:-1].split()[1][1:-1] - if qvtype.startswith(("PyQt4", "PyQt5")): - qtVariable = True - - if qtVariable: - vlist = self.__formatQtVariable(qvar, qvtype) - else: - # format the dictionary found - if dictkeys is None: - dictkeys = dict.keys() - else: - # treatment for sequences and dictionaries - if access: - exec "dict = dict%s" % access - else: - dict = dict[dictkeys[0]] - if isDict: - dictkeys = dict.keys() - else: - dictkeys = range(len(dict)) - vlist = self.__formatVariablesList( - dictkeys, dict, scope, filter, formatSequences) - varlist.extend(vlist) - - if obj is not None and not formatSequences: - try: - if unicode(repr(obj)).startswith('{'): - varlist.append(('...', 'dict', "%d" % len(obj.keys()))) - elif unicode(repr(obj)).startswith('['): - varlist.append(('...', 'list', "%d" % len(obj))) - elif unicode(repr(obj)).startswith('('): - varlist.append(('...', 'tuple', "%d" % len(obj))) - except Exception: - pass + break + typeObject, typeName, typeStr, resolver = \ + DebugVariables.getType(variable) + if typeStr.startswith(("PyQt5.", "PyQt4.")): + vlist = self.__formatQtVariable(variable, typeName) + varlist.extend(vlist) + elif resolver: + dict = resolver.getDictionary(variable) + vlist = self.__formatVariablesList( + list(dict.keys()), dict, scope, filter) + varlist.extend(vlist) self.sendJsonCommand("ResponseVariable", { "scope": scope, @@ -1649,17 +1461,32 @@ "variables": varlist, }) - def __formatQtVariable(self, value, vtype): + 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 """ - Private method to produce a formated output of a simple Qt4/Qt5 type. + for indicator in DebugClientBase.Indicators: + if var.endswith(indicator): + return var[:-len(indicator)], indicator + + return var, "" - @param value variable to be formated - @param vtype type of the variable to be formatted (string) + def __formatQtVariable(self, value, qttype): + """ + Private method to produce a formatted output of a simple Qt4/Qt5 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. """ - qttype = vtype.split('.')[-1] varlist = [] if qttype == 'QChar': varlist.append(("", "QChar", "%s" % unichr(value.unicode()))) @@ -1782,7 +1609,7 @@ return varlist def __formatVariablesList(self, keylist, dict, scope, filter=[], - formatSequences=0): + formatSequences=False): """ Private method to produce a formated variables list. @@ -1825,7 +1652,9 @@ continue # filter hidden attributes (filter #0) - if 0 in filter and unicode(key)[:2] == '__': + if 0 in filter and unicode(key)[:2] == '__' and not ( + key == "___len___" and + DebugVariables.TooLargeAttribute in keylist): continue # special handling for '__builtins__' (it's way too big) @@ -1835,7 +1664,7 @@ else: value = dict[key] valtypestr = ("%s" % type(value))[1:-1] - + if valtypestr.split(' ', 1)[0] == 'class': # handle new class type of python 2.2+ if ConfigVarTypeStrings.index('instance') in filter: @@ -1853,7 +1682,7 @@ continue elif valtype == "sip.methoddescriptor": if ConfigVarTypeStrings.index( - 'instance method') in filter: + 'method') in filter: continue elif valtype == "sip.enumtype": if ConfigVarTypeStrings.index('class') in filter: @@ -1863,7 +1692,8 @@ continue try: - if valtype not in ['list', 'tuple', 'dict']: + if valtype not in ['list', 'tuple', 'dict', 'set', + 'frozenset']: rvalue = repr(value) if valtype.startswith('class') and \ rvalue[0] in ['{', '(', '[']:
--- a/DebugClients/Python2/DebugConfig.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python2/DebugConfig.py Fri Sep 16 19:28:39 2016 +0200 @@ -7,15 +7,18 @@ Module defining type strings for the different Python types. """ +# +# Keep this list in sync with Debugger.Config.ConfigVarTypeFilters +# ConfigVarTypeStrings = [ '__', 'NoneType', 'type', 'bool', 'int', 'long', 'float', 'complex', 'str', 'unicode', 'tuple', 'list', 'dict', 'dict-proxy', 'set', 'file', 'xrange', 'slice', 'buffer', 'class', 'instance', - 'instance method', 'property', 'generator', + 'method', 'property', 'generator', 'function', 'builtin_function_or_method', 'code', 'module', - 'ellipsis', 'traceback', 'frame', 'other' + 'ellipsis', 'traceback', 'frame', 'other', 'frozenset' ] #
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Python2/DebugVariables.py Fri Sep 16 19:28:39 2016 +0200 @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing classes and functions to dump variable contents. +""" + +# +# This code was inspired by pydevd. +# + +MaxItemsToHandle = 300 +TooLargeMessage = ("Too large to show contents. Max items to show: " + + str(MaxItemsToHandle)) +TooLargeAttribute = "Too large to be handled." + +############################################################ +## Classes implementing resolvers for various compund types +############################################################ + + +class BaseResolver(object): + """ + Base class of the resolver class tree. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type any + @param attribute name of the attribute to extract + @type str + @return value of the attribute + @rtype any + @exception NotImplementedError raised to indicate a missing + implementation + """ # __IGNORE_WARNING_D235__ + raise NotImplementedError + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + @exception NotImplementedError raised to indicate a missing + implementation + """ # __IGNORE_WARNING_D235__ + raise NotImplementedError + + +class DefaultResolver(BaseResolver): + """ + Class used to resolve the default way. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type any + @param attribute name of the attribute to extract + @type str + @return value of the attribute + @rtype any + """ + return getattr(var, attribute) + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + names = dir(var) + if not names and hasattr(var, "__members__"): + names = var.__members__ + + d = {} + for name in names: + try: + attribute = getattr(var, name) + d[name] = attribute + except Exception: + pass # if we can't get it, simply ignore it + + return d + + +class DictResolver(BaseResolver): + """ + Class used to resolve from a dictionary. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type dict + @param attribute name of the attribute to extract + @type str + @return value of the attribute + @rtype any + """ + if attribute in ('___len___', TooLargeAttribute): + return None + + if "(ID:" not in attribute: + try: + return var[attribute] + except Exception: + return getattr(var, attribute) + + expectedID = int(attribute.split("(ID:")[-1][:-1]) + for key, value in var.items(): + if id(key) == expectedID: + return value + + return None + + def __keyToStr(self, key): + """ + Private method to get a string representation for a key. + + @param key key to be converted + @type any + @return string representation of the given key + @rtype str + """ + if isinstance(key, str): + return repr(key) + else: + return key + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + d = {} + count = 0 + for key, value in var.items(): + count += 1 + key = "%s (ID:%s)" % (self.__keyToStr(key), id(key)) + d[key] = value + if count > MaxItemsToHandle: + d[TooLargeAttribute] = TooLargeMessage + break + + d["___len___"] = len(var) + + # in case it has additional fields + additionals = defaultResolver.getDictionary(var) + d.update(additionals) + + return d + + +class ListResolver(BaseResolver): + """ + Class used to resolve from a tuple or list. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type tuple or list + @param attribute name of the attribute to extract + @type str + @return value of the attribute + @rtype any + """ + if attribute in ('___len___', TooLargeAttribute): + return None + + try: + return var[int(attribute)] + except Exception: + return getattr(var, attribute) + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + d = {} + count = 0 + for value in var: + d[str(count)] = value + count += 1 + if count > MaxItemsToHandle: + d[TooLargeAttribute] = TooLargeMessage + break + + d["___len___"] = len(var) + + # in case it has additional fields + additionals = defaultResolver.getDictionary(var) + d.update(additionals) + + return d + + +class SetResolver(BaseResolver): + """ + Class used to resolve from a set or frozenset. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type tuple or list + @param attribute id of the value to extract + @type str + @return value of the attribute + @rtype any + """ + if attribute in ('___len___', TooLargeAttribute): + return None + + if attribute.startswith("ID:"): + attribute = attribute.split(None, 1)[1] + try: + attribute = int(attribute) + except Exception: + return getattr(var, attribute) + + for v in var: + if id(v) == attribute: + return v + + return None + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + d = {} + count = 0 + for value in var: + count += 1 + d["ID: " + str(id(value))] = value + if count > MaxItemsToHandle: + d[TooLargeAttribute] = TooLargeMessage + break + + d["___len___"] = len(var) + + # in case it has additional fields + additionals = defaultResolver.getDictionary(var) + d.update(additionals) + + return d + + +defaultResolver = DefaultResolver() +dictResolver = DictResolver() +listResolver = ListResolver() +setResolver = SetResolver() + +# TODO: add resolver for numpy arrays +# TODO: add resolver for Django MultiValueDict +# TODO: add resolver for collections.deque + +############################################################ +## Methods to determine the type of a variable and the +## resolver class to use +############################################################ + +_TypeMap = None + + +def _initTypeMap(): + """ + Protected function to initialize the type map. + """ + global _TypeMap + + _TypeMap = [ + (type(None), None,), + (int, None), + (float, None), + (complex, None), + (str, None), + (tuple, listResolver), + (list, listResolver), + (dict, dictResolver), + ] + + try: + _TypeMap.append((long, None)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + + try: + _TypeMap.append((unicode, None)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + + try: + _TypeMap.append((set, setResolver)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + + try: + _TypeMap.append((frozenset, setResolver)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + + +def getType(obj): + """ + Public method to get the type information for an object. + + @param obj object to get type information for + @type any + @return tuple containing the type, type name, type string and resolver + @rtype tuple of type, str, str, BaseResolver + """ + typeObject = type(obj) + typeName = typeObject.__name__ + typeStr = str(typeObject)[8:-2] + + if typeStr.startswith(("PyQt5.", "PyQt4.")): + resolver = None + else: + if _TypeMap is None: + _initTypeMap() + + for typeData in _TypeMap: + if isinstance(obj, typeData[0]): + resolver = typeData[1] + break + else: + resolver = defaultResolver + + return typeObject, typeName, typeStr, resolver + +# +# eflag: FileType = Python2 +# eflag: noqa = M601,M702
--- a/DebugClients/Python3/DebugClientBase.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python3/DebugClientBase.py Fri Sep 16 19:28:39 2016 +0200 @@ -138,6 +138,9 @@ """ clientCapabilities = DebugClientCapabilities.HasAll + # keep these in sync with VariablesViewer.VariableItem.Indicators + Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING__ + def __init__(self): """ Constructor @@ -1433,8 +1436,7 @@ if scope != -1: variable = dict for attribute in var: - if attribute[-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__ - attribute = attribute[:-2] + attribute = self.__extractIndicators(attribute)[0] typeObject, typeName, typeStr, resolver = \ DebugVariables.getType(variable) if resolver: @@ -1458,6 +1460,22 @@ "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 Qt4/Qt5 type. @@ -1640,7 +1658,9 @@ continue # filter hidden attributes (filter #0) - if 0 in filter and str(key)[:2] == '__': + if 0 in filter and str(key)[:2] == '__' and not ( + key == "___len___" and + DebugVariables.TooLargeAttribute in keylist): continue # special handling for '__builtins__' (it's way too big) @@ -1684,7 +1704,8 @@ continue try: - if valtype not in ['list', 'tuple', 'dict']: + if valtype not in ['list', 'tuple', 'dict', 'set', + 'frozenset']: rvalue = repr(value) if valtype.startswith('class') and \ rvalue[0] in ['{', '(', '[']:
--- a/DebugClients/Python3/DebugConfig.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python3/DebugConfig.py Fri Sep 16 19:28:39 2016 +0200 @@ -7,6 +7,9 @@ Module defining type strings for the different Python types. """ +# +# Keep this list in sync with Debugger.Config.ConfigVarTypeFilters +# ConfigVarTypeStrings = [ '__', 'NoneType', 'type', 'bool', 'int', 'long', 'float', 'complex', @@ -15,7 +18,7 @@ 'slice', 'buffer', 'class', 'instance', 'method', 'property', 'generator', 'function', 'builtin_function_or_method', 'code', 'module', - 'ellipsis', 'traceback', 'frame', 'other' + 'ellipsis', 'traceback', 'frame', 'other', 'frozenset' ] #
--- a/DebugClients/Python3/DebugVariables.py Wed Sep 14 20:08:16 2016 +0200 +++ b/DebugClients/Python3/DebugVariables.py Fri Sep 16 19:28:39 2016 +0200 @@ -12,7 +12,7 @@ # MaxItemsToHandle = 300 -TooLargeMessage = ("Too large to show contents. Max items to show: " + +TooLargeMessage = ("Too large to show contents. Max items to show: " + str(MaxItemsToHandle)) TooLargeAttribute = "Too large to be handled." @@ -20,6 +20,7 @@ ## Classes implementing resolvers for various compund types ############################################################ + class BaseResolver(object): """ Base class of the resolver class tree. @@ -34,7 +35,9 @@ @type str @return value of the attribute @rtype any - """ + @exception NotImplementedError raised to indicate a missing + implementation + """ # __IGNORE_WARNING_D235__ raise NotImplementedError def getDictionary(self, var): @@ -45,7 +48,9 @@ @type any @return dictionary containing the variable attributes @rtype dict - """ + @exception NotImplementedError raised to indicate a missing + implementation + """ # __IGNORE_WARNING_D235__ raise NotImplementedError @@ -105,18 +110,16 @@ @return value of the attribute @rtype any """ - if attribute in ('__len__', TooLargeAttribute): + if attribute in ('___len___', TooLargeAttribute): return None - if "(" not in attribute: + if "(ID:" not in attribute: try: -## if attribute[0] == "'" and attribute[-1] == "'": -## attribute = attribute[1:-1] return var[attribute] except Exception: return getattr(var, attribute) - expectedID = int(attribute.split("(")[-1][:-1]) + expectedID = int(attribute.split("(ID:")[-1][:-1]) for key, value in var.items(): if id(key) == expectedID: return value @@ -124,6 +127,14 @@ return None def __keyToStr(self, key): + """ + Private method to get a string representation for a key. + + @param key key to be converted + @type any + @return string representation of the given key + @rtype str + """ if isinstance(key, str): return repr(key) else: @@ -142,13 +153,13 @@ count = 0 for key, value in var.items(): count += 1 - key = "{0} ({1})".format(self.__keyToStr(key), id(key)) + key = "{0} (ID:{1})".format(self.__keyToStr(key), id(key)) d[key] = value if count > MaxItemsToHandle: d[TooLargeAttribute] = TooLargeMessage break - d["__len__"] = len(var) + d["___len___"] = len(var) # in case it has additional fields additionals = defaultResolver.getDictionary(var) @@ -172,7 +183,7 @@ @return value of the attribute @rtype any """ - if attribute in ('__len__', TooLargeAttribute): + if attribute in ('___len___', TooLargeAttribute): return None try: @@ -189,18 +200,74 @@ @return dictionary containing the variable attributes @rtype dict """ - length = len(var) d = {} - formatStr = "{0:0" + str(len(str(length))) + "d}" count = 0 for value in var: - d[formatStr.format(count)] = value + d[str(count)] = value count += 1 if count > MaxItemsToHandle: d[TooLargeAttribute] = TooLargeMessage break - d["__len__"] = length + d["___len___"] = len(var) + + # in case it has additional fields + additionals = defaultResolver.getDictionary(var) + d.update(additionals) + + return d + + +class SetResolver(BaseResolver): + """ + Class used to resolve from a set or frozenset. + """ + def resolve(self, var, attribute): + """ + Public method to get an attribute from a variable. + + @param var variable to extract an attribute or value from + @type tuple or list + @param attribute id of the value to extract + @type str + @return value of the attribute + @rtype any + """ + if attribute in ('___len___', TooLargeAttribute): + return None + + if attribute.startswith("ID:"): + attribute = attribute.split(None, 1)[1] + try: + attribute = int(attribute) + except Exception: + return getattr(var, attribute) + + for v in var: + if id(v) == attribute: + return v + + return None + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + d = {} + count = 0 + for value in var: + count += 1 + d["ID: " + str(id(value))] = value + if count > MaxItemsToHandle: + d[TooLargeAttribute] = TooLargeMessage + break + + d["___len___"] = len(var) # in case it has additional fields additionals = defaultResolver.getDictionary(var) @@ -212,8 +279,8 @@ defaultResolver = DefaultResolver() dictResolver = DictResolver() listResolver = ListResolver() +setResolver = SetResolver() -# TODO: add resolver for set and frozenset # TODO: add resolver for numpy arrays # TODO: add resolver for Django MultiValueDict # TODO: add resolver for collections.deque @@ -225,9 +292,10 @@ _TypeMap = None + def _initTypeMap(): """ - Protected function to initialize the type map + Protected function to initialize the type map. """ global _TypeMap @@ -252,6 +320,16 @@ except Exception: pass # not available on all python versions + try: + _TypeMap.append((set, setResolver)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + + try: + _TypeMap.append((frozenset, setResolver)) # __IGNORE_WARNING__ + except Exception: + pass # not available on all python versions + def getType(obj): """ @@ -280,3 +358,6 @@ resolver = defaultResolver return typeObject, typeName, typeStr, resolver + +# +# eflag: noqa = M702
--- a/Debugger/Config.py Wed Sep 14 20:08:16 2016 +0200 +++ b/Debugger/Config.py Fri Sep 16 19:28:39 2016 +0200 @@ -9,51 +9,80 @@ from __future__ import unicode_literals -try: - from PyQt5.QtCore import QT_TRANSLATE_NOOP - - # Variables type definition - ConfigVarTypeDispStrings = [ - QT_TRANSLATE_NOOP('Variable Types', 'Hidden Attributes'), - QT_TRANSLATE_NOOP('Variable Types', 'None'), - QT_TRANSLATE_NOOP('Variable Types', 'Type'), - QT_TRANSLATE_NOOP('Variable Types', 'Boolean'), - QT_TRANSLATE_NOOP('Variable Types', 'Integer'), - QT_TRANSLATE_NOOP('Variable Types', 'Long Integer'), - QT_TRANSLATE_NOOP('Variable Types', 'Float'), - QT_TRANSLATE_NOOP('Variable Types', 'Complex'), - QT_TRANSLATE_NOOP('Variable Types', 'String'), - QT_TRANSLATE_NOOP('Variable Types', 'Unicode String'), - QT_TRANSLATE_NOOP('Variable Types', 'Tuple'), - QT_TRANSLATE_NOOP('Variable Types', 'List/Array'), - QT_TRANSLATE_NOOP('Variable Types', 'Dictionary/Hash/Map'), - QT_TRANSLATE_NOOP('Variable Types', 'Dictionary Proxy'), - QT_TRANSLATE_NOOP('Variable Types', 'Set'), - QT_TRANSLATE_NOOP('Variable Types', 'File'), - QT_TRANSLATE_NOOP('Variable Types', 'X Range'), - QT_TRANSLATE_NOOP('Variable Types', 'Slice'), - QT_TRANSLATE_NOOP('Variable Types', 'Buffer'), - QT_TRANSLATE_NOOP('Variable Types', 'Class'), - QT_TRANSLATE_NOOP('Variable Types', 'Class Instance'), - QT_TRANSLATE_NOOP('Variable Types', 'Class Method'), - QT_TRANSLATE_NOOP('Variable Types', 'Class Property'), - QT_TRANSLATE_NOOP('Variable Types', 'Generator'), - QT_TRANSLATE_NOOP('Variable Types', 'Function'), +# TODO: change these to dictionaries with keys according to DebugConfig.py +from PyQt5.QtCore import QT_TRANSLATE_NOOP + +# Variables type definition +ConfigVarTypeDispStrings = { + '__': QT_TRANSLATE_NOOP('Variable Types', 'Hidden Attributes'), + 'NoneType': QT_TRANSLATE_NOOP('Variable Types', 'None'), + 'type': QT_TRANSLATE_NOOP('Variable Types', 'Type'), + 'bool': QT_TRANSLATE_NOOP('Variable Types', 'Boolean'), + 'int': QT_TRANSLATE_NOOP('Variable Types', 'Integer'), + 'long': QT_TRANSLATE_NOOP('Variable Types', 'Long Integer'), + 'float': QT_TRANSLATE_NOOP('Variable Types', 'Float'), + 'complex': QT_TRANSLATE_NOOP('Variable Types', 'Complex'), + 'str': QT_TRANSLATE_NOOP('Variable Types', 'String'), + 'unicode': QT_TRANSLATE_NOOP('Variable Types', 'Unicode String'), + 'tuple': QT_TRANSLATE_NOOP('Variable Types', 'Tuple'), + 'list': QT_TRANSLATE_NOOP('Variable Types', 'List/Array'), + 'dict': QT_TRANSLATE_NOOP('Variable Types', 'Dictionary/Hash/Map'), + 'dict-proxy': QT_TRANSLATE_NOOP('Variable Types', 'Dictionary Proxy'), + 'set': QT_TRANSLATE_NOOP('Variable Types', 'Set'), + 'frozenset': QT_TRANSLATE_NOOP('Variable Types', 'Frozen Set'), + 'file': QT_TRANSLATE_NOOP('Variable Types', 'File'), + 'xrange': QT_TRANSLATE_NOOP('Variable Types', 'X Range'), + 'slice': QT_TRANSLATE_NOOP('Variable Types', 'Slice'), + 'buffer': QT_TRANSLATE_NOOP('Variable Types', 'Buffer'), + 'class': QT_TRANSLATE_NOOP('Variable Types', 'Class'), + 'instance': QT_TRANSLATE_NOOP('Variable Types', 'Class Instance'), + 'method': QT_TRANSLATE_NOOP('Variable Types', 'Class Method'), + 'property': QT_TRANSLATE_NOOP('Variable Types', 'Class Property'), + 'generator': QT_TRANSLATE_NOOP('Variable Types', 'Generator'), + 'function': QT_TRANSLATE_NOOP('Variable Types', 'Function'), + 'builtin_function_or_method': QT_TRANSLATE_NOOP('Variable Types', 'Builtin Function'), - QT_TRANSLATE_NOOP('Variable Types', 'Code'), - QT_TRANSLATE_NOOP('Variable Types', 'Module'), - QT_TRANSLATE_NOOP('Variable Types', 'Ellipsis'), - QT_TRANSLATE_NOOP('Variable Types', 'Traceback'), - QT_TRANSLATE_NOOP('Variable Types', 'Frame'), - QT_TRANSLATE_NOOP('Variable Types', 'Other') - ] -except ImportError: - # Variables type definition (for non-Qt only) - ConfigVarTypeDispStrings = [ - 'Hidden Attributes', 'None', 'Type', 'Boolean', 'Integer', - 'Long Integer', 'Float', 'Complex', 'String', 'Unicode String', - 'Tuple', 'List/Array', 'Dictionary/Hash/Map', 'Dictionary Proxy', - 'Set', 'File', 'X Range', 'Slice', 'Buffer', 'Class', - 'Class Instance', 'Class Method', 'Class Property', 'Generator', - 'Function', 'Builtin Function', 'Code', 'Module', 'Ellipsis', - 'Traceback', 'Frame', 'Other'] + 'code': QT_TRANSLATE_NOOP('Variable Types', 'Code'), + 'module': QT_TRANSLATE_NOOP('Variable Types', 'Module'), + 'ellipsis': QT_TRANSLATE_NOOP('Variable Types', 'Ellipsis'), + 'traceback': QT_TRANSLATE_NOOP('Variable Types', 'Traceback'), + 'frame': QT_TRANSLATE_NOOP('Variable Types', 'Frame'), + 'other': QT_TRANSLATE_NOOP('Variable Types', 'Other'), +} + + +ConfigVarTypeFilters = { + '__': 0, + 'NoneType': 1, + 'type': 2, + 'bool': 3, + 'int': 4, + 'long': 5, + 'float': 6, + 'complex': 7, + 'str': 8, + 'unicode': 9, + 'tuple': 10, + 'list': 11, + 'dict': 12, + 'dict-proxy': 13, + 'set': 14, + 'file': 15, + 'xrange': 16, + 'slice': 17, + 'buffer': 18, + 'class': 19, + 'instance': 20, + 'method': 21, + 'property': 22, + 'generator': 23, + 'function': 24, + 'builtin_function_or_method': 25, + 'code': 26, + 'module': 27, + 'ellipsis': 28, + 'traceback': 29, + 'frame': 30, + 'other': 31, + 'frozenset': 32, +}
--- a/Debugger/VariablesFilterDialog.py Wed Sep 14 20:08:16 2016 +0200 +++ b/Debugger/VariablesFilterDialog.py Fri Sep 16 19:28:39 2016 +0200 @@ -9,9 +9,10 @@ from __future__ import unicode_literals -from PyQt5.QtWidgets import QDialog, QDialogButtonBox +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem -from Debugger.Config import ConfigVarTypeDispStrings +from Debugger.Config import ConfigVarTypeDispStrings, ConfigVarTypeFilters import Preferences from .Ui_VariablesFilterDialog import Ui_VariablesFilterDialog @@ -41,19 +42,15 @@ self.defaultButton = self.buttonBox.addButton( self.tr("Save Default"), QDialogButtonBox.ActionRole) - lDefaultFilter, gDefaultFilter = Preferences.getVarFilters() + #populate the list widgets and set the default selection + for widget in self.localsList, self.globalsList: + for varType, varTypeStr in ConfigVarTypeDispStrings.items(): + itm = QListWidgetItem(self.tr(varTypeStr), widget) + itm.setData(Qt.UserRole, ConfigVarTypeFilters[varType]) + widget.addItem(itm) - #populate the listboxes and set the default selection - for lb in self.localsList, self.globalsList: - for ts in ConfigVarTypeDispStrings: - lb.addItem(self.tr(ts)) - - for filterIndex in lDefaultFilter: - itm = self.localsList.item(filterIndex) - itm.setSelected(True) - for filterIndex in gDefaultFilter: - itm = self.globalsList.item(filterIndex) - itm.setSelected(True) + lDefaultFilter, gDefaultFilter = Preferences.getVarFilters() + self.setSelection(lDefaultFilter, gDefaultFilter) def getSelection(self): """ @@ -63,17 +60,18 @@ locals variables filter, the second the globals variables filter. """ lList = [] - gList = [] for row in range(self.localsList.count()): itm = self.localsList.item(row) if itm.isSelected(): - lList.append(row) + lList.append(itm.data(Qt.UserRole)) + + gList = [] for row in range(self.globalsList.count()): itm = self.globalsList.item(row) if itm.isSelected(): - gList.append(row) + gList.append(itm.data(Qt.UserRole)) return (lList, gList) - + def setSelection(self, lList, gList): """ Public slot to set the current selection. @@ -83,10 +81,11 @@ """ for row in range(self.localsList.count()): itm = self.localsList.item(row) - itm.setSelected(row in lList) + itm.setSelected(itm.data(Qt.UserRole) in lList) + for row in range(self.globalsList.count()): itm = self.globalsList.item(row) - itm.setSelected(row in gList) + itm.setSelected(itm.data(Qt.UserRole) in gList) def on_buttonBox_clicked(self, button): """
--- a/Debugger/VariablesFilterDialog.ui Wed Sep 14 20:08:16 2016 +0200 +++ b/Debugger/VariablesFilterDialog.ui Fri Sep 16 19:28:39 2016 +0200 @@ -1,8 +1,9 @@ -<ui version="4.0" > - <author>Detlev Offenbach <detlev@die-offenbachs.de></author> +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <author>Detlev Offenbach <detlev@die-offenbachs.de></author> <class>VariablesFilterDialog</class> - <widget class="QDialog" name="VariablesFilterDialog" > - <property name="geometry" > + <widget class="QDialog" name="VariablesFilterDialog"> + <property name="geometry"> <rect> <x>0</x> <y>0</y> @@ -10,78 +11,90 @@ <height>338</height> </rect> </property> - <property name="windowTitle" > + <property name="windowTitle"> <string>Variables Type Filter</string> </property> - <property name="whatsThis" > - <string><b>Filter Dialog</b> -<p> This dialog gives the user the possibility to select what kind of variables should <b>not</b> be shown during a debugging session.</p></string> + <property name="whatsThis"> + <string><b>Filter Dialog</b> +<p> This dialog gives the user the possibility to select what kind of variables should <b>not</b> be shown during a debugging session.</p></string> </property> - <property name="sizeGripEnabled" > + <property name="sizeGripEnabled"> <bool>true</bool> </property> - <layout class="QGridLayout" > - <item row="2" column="0" colspan="2" > - <widget class="QDialogButtonBox" name="buttonBox" > - <property name="orientation" > + <layout class="QGridLayout"> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="standardButtons" > + <property name="standardButtons"> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> - <item row="0" column="0" > - <widget class="QLabel" name="localsLabel" > - <property name="text" > + <item row="0" column="0"> + <widget class="QLabel" name="localsLabel"> + <property name="text"> <string>&Locals Filter</string> </property> - <property name="buddy" > + <property name="buddy"> <cstring>localsList</cstring> </property> </widget> </item> - <item row="0" column="1" > - <widget class="QLabel" name="globalsLabel" > - <property name="text" > + <item row="0" column="1"> + <widget class="QLabel" name="globalsLabel"> + <property name="text"> <string>&Globals Filter</string> </property> - <property name="buddy" > + <property name="buddy"> <cstring>globalsList</cstring> </property> </widget> </item> - <item row="1" column="0" > - <widget class="QListWidget" name="localsList" > - <property name="toolTip" > + <item row="1" column="0"> + <widget class="QListWidget" name="localsList"> + <property name="toolTip"> <string>Locals Filter List</string> </property> - <property name="whatsThis" > - <string><b>Locals Filter List</b> -<p>Select the variable types you want to be filtered out of the locals variables list.</p<</string> + <property name="whatsThis"> + <string><b>Locals Filter List</b> +<p>Select the variable types you want to be filtered out of the locals variables list.</p<</string> </property> - <property name="selectionMode" > + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> <enum>QAbstractItemView::ExtendedSelection</enum> </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> </widget> </item> - <item row="1" column="1" > - <widget class="QListWidget" name="globalsList" > - <property name="toolTip" > + <item row="1" column="1"> + <widget class="QListWidget" name="globalsList"> + <property name="toolTip"> <string>Globals Filter List</string> </property> - <property name="whatsThis" > - <string><b>Globals Filter List</b> -<p>Select the variable types you want to be filtered out of the globals variables list.</p<</string> + <property name="whatsThis"> + <string><b>Globals Filter List</b> +<p>Select the variable types you want to be filtered out of the globals variables list.</p<</string> </property> - <property name="selectionMode" > + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> <enum>QAbstractItemView::ExtendedSelection</enum> </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> </widget> </item> </layout> </widget> - <layoutdefault spacing="6" margin="11" /> + <layoutdefault spacing="6" margin="11"/> <pixmapfunction>qPixmapFromMimeSource</pixmapfunction> <tabstops> <tabstop>localsList</tabstop> @@ -95,11 +108,11 @@ <receiver>VariablesFilterDialog</receiver> <slot>accept()</slot> <hints> - <hint type="sourcelabel" > + <hint type="sourcelabel"> <x>14</x> <y>319</y> </hint> - <hint type="destinationlabel" > + <hint type="destinationlabel"> <x>15</x> <y>332</y> </hint> @@ -111,11 +124,11 @@ <receiver>VariablesFilterDialog</receiver> <slot>reject()</slot> <hints> - <hint type="sourcelabel" > + <hint type="sourcelabel"> <x>84</x> <y>317</y> </hint> - <hint type="destinationlabel" > + <hint type="destinationlabel"> <x>84</x> <y>336</y> </hint>
--- a/Debugger/VariablesViewer.py Wed Sep 14 20:08:16 2016 +0200 +++ b/Debugger/VariablesViewer.py Fri Sep 16 19:28:39 2016 +0200 @@ -16,12 +16,9 @@ from PyQt5.QtCore import Qt, QRegExp, qVersion, QCoreApplication from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, \ QMenu -from PyQt5.QtGui import QTextDocument # __IGNORE_WARNING__ from E5Gui.E5Application import e5App -from DebugClients.Python3.DebugConfig import ConfigVarTypeStrings - from .Config import ConfigVarTypeDispStrings import Preferences @@ -32,6 +29,16 @@ """ Class implementing the data structure for variable items. """ + Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING_M613__ + Type2Indicators = { + # Python types + 'list': '[]', + 'tuple': '()', + 'dict': '{:}', # __IGNORE_WARNING_M613__ + 'set': '{}', # __IGNORE_WARNING_M613__ + 'frozenset': '{}', # __IGNORE_WARNING_M613__ + } + def __init__(self, parent, dvar, dvalue, dtype): """ Constructor @@ -41,6 +48,8 @@ @param dvalue value string (string) @param dtype type string (string) """ + dvar, self.__varID = VariableItem.extractId(dvar) + self.__value = dvalue if len(dvalue) > 2048: # 1024 * 2 dvalue = QCoreApplication.translate( @@ -77,7 +86,71 @@ @return value of the item (string) """ return self.__value + + def getId(self): + """ + Public method to get the ID string. + @return ID string + @rtype str + """ + return self.__varID + + @classmethod + def extractId(cls, var): + """ + Class method to extract the ID string from a variable text. + + @param var variable text + @type str + @return tuple containing the variable text without ID and the ID string + @rtype tuple of two str + """ + if " (ID:" in var: + dvar, varID = var.rsplit(None, 1) + if varID.endswith(VariableItem.Indicators): + varID, indicators = VariableItem.extractIndicators(varID) + dvar += indicators + else: + dvar = var + varID = None + + return dvar, varID + + @classmethod + def extractIndicators(cls, var): + """ + Class 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 VariableItem.Indicators: + if var.endswith(indicator): + return var[:-len(indicator)], indicator + + return var, "" + + def _buildKey(self): + """ + Protected method to build the access key for the variable. + + @return access key + @type str + """ + indicators = "" + txt = self.text(0) + if txt.endswith(VariableItem.Indicators): + txt, indicators = VariableItem.extractIndicators(txt) + if self.__varID: + txt = "{0} {1}{2}".format(txt, self.__varID, indicators) + else: + txt = "{0}{1}".format(txt, indicators) + return txt + def data(self, column, role): """ Public method to return the data for the requested role. @@ -156,12 +229,12 @@ self.deleteChildren() self.populated = True - pathlist = [self.text(0)] + pathlist = [self._buildKey()] par = self.parent() # step 1: get a pathlist up to the requested variable while par is not None: - pathlist.insert(0, par.text(0)) + pathlist.insert(0, par._buildKey()) par = par.parent() # step 2: request the variable from the debugger @@ -221,9 +294,9 @@ element 2 will have a key of '000002' and appear before element 10 with a key of '000010' """ - col0Str = self.text(0)[:-2] # strip off [], () or {} - indicator = self.text(0)[-2:] - self.setText(0, "{0:6d}{1}".format(int(col0Str), indicator)) + # strip off [], () or {} + col0Str, indicators = VariableItem.extractIndicators(self.text(0)) + self.setText(0, "{0:6d}{1}".format(int(col0Str), indicators)) class VariablesViewer(QTreeWidget): @@ -254,14 +327,6 @@ self.__debugViewer = viewer self.__globalScope = globalScope - self.indicators = { - # Python types - 'list': '[]', - 'tuple': '()', - 'dict': '{}', # __IGNORE_WARNING__ - - } - self.rx_class = QRegExp('<.*(instance|object) at 0x.*>') self.rx_class2 = QRegExp('class .*') self.rx_class3 = QRegExp('<class .* at 0x.*>') @@ -370,12 +435,17 @@ else: count = node.childCount() + if column == 0: + searchStr = VariableItem.extractId(slist[0])[0] + else: + searchStr = slist[0] + for index in range(count): if node is None: itm = self.topLevelItem(index) else: itm = node.child(index) - if itm.text(column) == slist[0]: + if itm.text(column) == searchStr: if len(slist) > 1: itm = self.__findItem(slist[1:], column, itm) return itm @@ -542,12 +612,12 @@ if parent is None: parent = self try: - dvar = '{0}{1}'.format(var, self.indicators[vtype]) + dvar = '{0}{1}'.format(var, VariableItem.Type2Indicators[vtype]) except KeyError: dvar = var dvtype = self.__getDispType(vtype) - if vtype in ['list', 'Array', 'tuple', 'dict', 'Hash']: + if vtype in ['list', 'tuple', 'dict', 'set', 'frozenset']: itm = self.__generateItem(parent, dvar, self.tr("{0} items").format(value), dvtype, True) @@ -575,12 +645,10 @@ @return displaystring (string) """ try: - i = ConfigVarTypeStrings.index(vtype) - dvtype = self.tr(ConfigVarTypeDispStrings[i]) - except ValueError: + dvtype = self.tr(ConfigVarTypeDispStrings[vtype]) + except KeyError: if vtype == 'classobj': - dvtype = self.tr(ConfigVarTypeDispStrings[ - ConfigVarTypeStrings.index('instance')]) + dvtype = self.tr(ConfigVarTypeDispStrings['instance']) else: dvtype = vtype return dvtype @@ -628,21 +696,20 @@ return # do not display anything, if the variable has no value vtype = itm.text(2) - name = itm.text(0) - if name[-2:] in ['[]', '{}', '()']: # __IGNORE_WARNING__ - name = name[:-2] + name = VariableItem.extractIndicators(itm.text(0).strip())[0] par = itm.parent() nlist = [name] # build up the fully qualified name while par is not None: - pname = par.text(0) - if pname[-2:] in ['[]', '{}', '()']: # __IGNORE_WARNING__ + pname, indicators = VariableItem.extractIndicators( + par.text(0).strip()) + if indicators: if nlist[0].endswith("."): nlist[0] = '[{0}].'.format(nlist[0][:-1]) else: nlist[0] = '[{0}]'.format(nlist[0]) - nlist.insert(0, pname[:-2]) + nlist.insert(0, pname) else: nlist.insert(0, '{0}.'.format(pname)) par = par.parent()
--- a/eric6.e4p Wed Sep 14 20:08:16 2016 +0200 +++ b/eric6.e4p Fri Sep 16 19:28:39 2016 +0200 @@ -36,6 +36,7 @@ <Source>DebugClients/Python2/DebugConfig.py</Source> <Source>DebugClients/Python2/DebugThread.py</Source> <Source>DebugClients/Python2/DebugUtilities.py</Source> + <Source>DebugClients/Python2/DebugVariables.py</Source> <Source>DebugClients/Python2/FlexCompleter.py</Source> <Source>DebugClients/Python2/PyProfile.py</Source> <Source>DebugClients/Python2/__init__.py</Source>