DebugClients/Python3/DebugClientBase.py

branch
debugger speed
changeset 5174
8c48f5e0cd92
parent 5170
fb9168c2e069
parent 5171
f1e9eebd5469
--- a/DebugClients/Python3/DebugClientBase.py	Thu Sep 15 21:49:13 2016 +0200
+++ b/DebugClients/Python3/DebugClientBase.py	Sun Sep 18 21:35:53 2016 +0200
@@ -13,19 +13,20 @@
 import codeop
 import traceback
 import os
+import json
 import imp
 import re
 import atexit
 import signal
 
 
-import DebugProtocol
 import DebugClientCapabilities
+import DebugVariables
 from DebugBase import setRecursionLimit, printerr   # __IGNORE_WARNING__
 from AsyncFile import AsyncFile, AsyncPendingWrite
 from DebugConfig import ConfigVarTypeStrings
 from FlexCompleter import Completer
-from DebugUtilities import getargvalues, formatargvalues
+from DebugUtilities import getargvalues, formatargvalues, prepareJsonCommand
 from BreakpointWatch import Breakpoint, Watch
 
 
@@ -123,27 +124,11 @@
     Class implementing the client side of the debugger.
 
     It provides access to the Python interpeter from a debugger running in
-    another process whether or not the Qt event loop is running.
-
-    The protocol between the debugger and the client assumes that there will be
-    a single source of debugger commands and a single source of Python
-    statements.  Commands and statement are always exactly one line and may be
-    interspersed.
-
-    The protocol is as follows.  First the client opens a connection to the
-    debugger and then sends a series of one line commands.  A command is either
-    >Load<, >Step<, >StepInto<, ... or a Python statement.
-    See DebugProtocol.py for a listing of valid protocol tokens.
-
-    A Python statement consists of the statement to execute, followed (in a
-    separate line) by >OK?<. If the statement was incomplete then the
-    response is >Continue<. If there was an exception then the response
-    is >Exception<. Otherwise the response is >OK<.  The reason
-    for the >OK?< part is to provide a sentinal (ie. the responding
-    >OK<) after any possible output as a result of executing the command.
-
-    The client may send any other lines at any other time which should be
-    interpreted as program output.
+    another process.
+    
+    The protocol between the debugger and the client is based on JSONRPC 2.0
+    PDUs. Each one is sent on a single line, i.e. commands or responses are
+    separated by a linefeed character.
 
     If the debugger closes the session there is no response from the client.
     The client may close the session at any time as a result of the script
@@ -154,12 +139,16 @@
     """
     clientCapabilities = DebugClientCapabilities.HasAll
     
+    # keep these in sync with VariablesViewer.VariableItem.Indicators
+    Indicators = ("()", "[]", "{:}", "{}")         # __IGNORE_WARNING__
+    
     def __init__(self):
         """
         Constructor
         """
         self.breakpoints = {}
         self.redirect = True
+        self.__receiveBuffer = ""
 
         # The next couple of members are needed for the threaded version.
         # For this base class they contain static values for the non threaded
@@ -188,11 +177,8 @@
         self.globalsFilterObjects = []
         self.localsFilterObjects = []
 
-        self.pendingResponse = DebugProtocol.ResponseOK
         self._fncache = {}
         self.dircache = []
-        self.inRawMode = False
-        self.mainProcStr = None     # used for the passive mode
         self.passive = False        # used to indicate the passive mode
         self.running = None
         self.test = None
@@ -262,7 +248,7 @@
         """
         Public method to setup a thread for DebugClient to debug.
         
-        If mainThread is non-zero, then we are attaching to the already
+        If mainThread is True, then we are attaching to the already
         started mainthread of the app and the rest of the args are ignored.
         
         @param target the start function of the target thread (i.e. the user
@@ -299,8 +285,10 @@
                 d["broken"] = False
             threadList.append(d)
         
-        self.write("{0}{1!r}\n".format(DebugProtocol.ResponseThreadList,
-                                       (currentId, threadList)))
+        self.sendJsonCommand("ResponseThreadList", {
+            "currentID": currentId,
+            "threadList": threadList,
+        })
     
     def input(self, prompt, echo=True):
         """
@@ -310,20 +298,13 @@
         @param echo Flag indicating echoing of the input (boolean)
         @return the entered string
         """
-        self.write("{0}{1!r}\n".format(
-            DebugProtocol.ResponseRaw, (prompt, echo)))
-        self.inRawMode = True
+        self.sendJsonCommand("RequestRaw", {
+            "prompt": prompt,
+            "echo": echo,
+        })
         self.eventLoop(True)
         return self.rawLine
 
-    def __exceptionRaised(self):
-        """
-        Private method called in the case of an exception.
-        
-        It ensures that the debug server is informed of the raised exception.
-        """
-        self.pendingResponse = DebugProtocol.ResponseException
-    
     def sessionClose(self, exit=True):
         """
         Public method to close the session with the debugger and optionally
@@ -336,8 +317,6 @@
         except Exception:
             pass
 
-        # clean up asyncio.
-        self.disconnect()
         self.debugging = False
         
         # make sure we close down our end of the socket
@@ -369,15 +348,14 @@
             try:
                 message = str(excval)
                 filename = excval.filename
-                linenr = excval.lineno
-                charnr = excval.offset
+                lineno = excval.lineno
+                charno = excval.offset
             except (AttributeError, ValueError):
-                exclist = []
-            else:
-                exclist = [message, [filename, linenr, charnr]]
-            
-            self.write("{0}{1}\n".format(
-                DebugProtocol.ResponseSyntax, str(exclist)))
+                message = ""
+                filename = ""
+                lineno = 0
+                charno = 0
+            self.sendSyntaxError(message, filename, lineno, charno)
             return None
         
         return code
@@ -396,635 +374,652 @@
             line = line[:-1]
 
 ##        printerr(line)          ##debug
-
-        eoc = line.find('<')
-
-        if eoc >= 0 and line[0] == '>':
-            # Get the command part and any argument.
-            cmd = line[:eoc + 1]
-            arg = line[eoc + 1:]
-            
-            if cmd == DebugProtocol.RequestVariables:
-                frmnr, scope, filter = eval(arg.replace("u'", "'"))
-                self.__dumpVariables(int(frmnr), int(scope), filter)
-                return
-            
-            if cmd == DebugProtocol.RequestVariable:
-                var, frmnr, scope, filter = eval(arg.replace("u'", "'"))
-                self.__dumpVariable(var, int(frmnr), int(scope), filter)
-                return
-            
-            if cmd == DebugProtocol.RequestThreadList:
-                self.__dumpThreadList()
-                return
+        
+        self.handleJsonCommand(line)
+    
+    def handleJsonCommand(self, jsonStr):
+        """
+        Public method to handle a command serialized as a JSON string.
+        
+        @param jsonStr string containing the command received from the IDE
+        @type str
+        """
+        try:
+            commandDict = json.loads(jsonStr.strip())
+        except (TypeError, ValueError) as err:
+            printerr(str(err))
+            return
+        
+        method = commandDict["method"]
+        params = commandDict["params"]
+        
+        if method == "RequestVariables":
+            self.__dumpVariables(
+                params["frameNumber"], params["scope"], params["filters"])
+        
+        elif method == "RequestVariable":
+            self.__dumpVariable(
+                params["variable"], params["frameNumber"],
+                params["scope"], params["filters"])
+        
+        elif method == "RequestThreadList":
+            self.__dumpThreadList()
+        
+        elif method == "RequestThreadSet":
+            if params["threadID"] in self.threads:
+                self.setCurrentThread(params["threadID"])
+                self.sendJsonCommand("ResponseThreadSet", {})
+                stack = self.currentThread.getStack()
+                self.sendJsonCommand("ResponseStack", {
+                    "stack": stack,
+                })
+        
+        elif method == "RequestCapabilities":
+            self.sendJsonCommand("ResponseCapabilities", {
+                "capabilities": self.__clientCapabilities(),
+                "clientType": "Python3"
+            })
+        
+        elif method == "RequestBanner":
+            self.sendJsonCommand("ResponseBanner", {
+                "version": "Python {0}".format(sys.version),
+                "platform": socket.gethostname(),
+                "dbgclient": self.variant,
+            })
+        
+        elif method == "RequestSetFilter":
+            self.__generateFilterObjects(params["scope"], params["filter"])
+        
+        elif method == "RequestCallTrace":
+            if params["enable"]:
+                callTraceEnabled = self.profile
+            else:
+                callTraceEnabled = None
             
-            if cmd == DebugProtocol.RequestThreadSet:
-                tid = eval(arg)
-                if tid in self.threads:
-                    self.setCurrentThread(tid)
-                    self.write(DebugProtocol.ResponseThreadSet + '\n')
-                    stack = self.currentThread.getStack()
-                    self.write('{0}{1!r}\n'.format(
-                        DebugProtocol.ResponseStack, stack))
-                return
-            
-            if cmd == DebugProtocol.RequestStep:
-                self.currentThread.step(True)
-                self.eventExit = True
-                return
-
-            if cmd == DebugProtocol.RequestStepOver:
-                self.currentThread.step(False)
-                self.eventExit = True
-                return
-            
-            if cmd == DebugProtocol.RequestStepOut:
-                self.currentThread.stepOut()
-                self.eventExit = True
-                return
-            
-            if cmd == DebugProtocol.RequestStepQuit:
-                if self.passive:
-                    self.progTerminated(42)
+            if self.debugging:
+                sys.setprofile(callTraceEnabled)
+            else:
+                # remember for later
+                self.callTraceEnabled = callTraceEnabled
+        
+        elif method == "RequestEnvironment":
+            for key, value in params["environment"].items():
+                if key.endswith("+"):
+                    if key[:-1] in os.environ:
+                        os.environ[key[:-1]] += value
+                    else:
+                        os.environ[key[:-1]] = value
                 else:
-                    self.set_quit()
-                    self.eventExit = True
-                return
-
-            if cmd == DebugProtocol.RequestContinue:
-                special = int(arg)
-                self.currentThread.go(special)
-                self.eventExit = True
-                return
-
-            if cmd == DebugProtocol.RequestOK:
-                self.write(self.pendingResponse + '\n')
-                self.pendingResponse = DebugProtocol.ResponseOK
-                return
-
-            if cmd == DebugProtocol.RequestCallTrace:
-                if arg.strip().lower() == "on":
-                    callTraceEnabled = self.profile
-                else:
-                    callTraceEnabled = None
-                if self.debugging:
-                    sys.setprofile(callTraceEnabled)
-                else:
-                    # remember for later
-                    self.callTraceEnabled = callTraceEnabled
-                    
-                return
+                    os.environ[key] = value
+        
+        elif method == "RequestLoad":
+            self._fncache = {}
+            self.dircache = []
+            sys.argv = []
+            self.__setCoding(params["filename"])
+            sys.argv.append(params["filename"])
+            sys.argv.extend(params["argv"])
+            sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
+            if params["workdir"] == '':
+                os.chdir(sys.path[1])
+            else:
+                os.chdir(params["workdir"])
+            
+            self.running = sys.argv[0]
+            self.mainFrame = None
+            self.debugging = True
+            
+            self.fork_auto = params["autofork"]
+            self.fork_child = params["forkChild"]
             
-            if cmd == DebugProtocol.RequestEnv:
-                env = eval(arg.replace("u'", "'"))
-                for key, value in env.items():
-                    if key.endswith("+"):
-                        if key[:-1] in os.environ:
-                            os.environ[key[:-1]] += value
-                        else:
-                            os.environ[key[:-1]] = value
-                    else:
-                        os.environ[key] = value
-                return
-
-            if cmd == DebugProtocol.RequestLoad:
-                self._fncache = {}
-                self.dircache = []
-                sys.argv = []
-                wd, fn, args, tracePython = arg.split('|')
-                self.__setCoding(fn)
-                sys.argv.append(fn)
-                sys.argv.extend(eval(args.replace("u'", "'")))
-                sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
-                if wd == '':
-                    os.chdir(sys.path[1])
-                else:
-                    os.chdir(wd)
-                self.running = sys.argv[0]
-                self.mainFrame = None
-                self.inRawMode = False
-                self.debugging = True
-                
-                self.threads.clear()
-                self.attachThread(mainThread=True)
-                
-                # set the system exception handling function to ensure, that
-                # we report on all unhandled exceptions
-                sys.excepthook = self.__unhandled_exception
-                self.__interceptSignals()
-                
-                # clear all old breakpoints, they'll get set after we have
-                # started
-                Breakpoint.clear_all_breaks()
-                Watch.clear_all_watches()
-                
-                self.mainThread.tracePythonLibs(int(tracePython))
-                
-                # This will eventually enter a local event loop.
-                # Note the use of backquotes to cause a repr of self.running.
-                # The need for this is on Windows os where backslash is the
-                # path separator. They will get inadvertantly stripped away
-                # during the eval causing IOErrors, if self.running is passed
-                # as a normal str.
-                self.debugMod.__dict__['__file__'] = self.running
-                sys.modules['__main__'] = self.debugMod
-                code = self.__compileFileSource(self.running)
-                if code:
-                    sys.setprofile(self.callTraceEnabled)
-                    res = self.mainThread.run(code, self.debugMod.__dict__)
-                    self.progTerminated(res)
-                return
+            self.threads.clear()
+            self.attachThread(mainThread=True)
+            
+            # set the system exception handling function to ensure, that
+            # we report on all unhandled exceptions
+            sys.excepthook = self.__unhandled_exception
+            self.__interceptSignals()
+            
+            # clear all old breakpoints, they'll get set after we have
+            # started
+            Breakpoint.clear_all_breaks()
+            Watch.clear_all_watches()
+            
+            self.mainThread.tracePythonLibs(params["traceInterpreter"])
+            
+            # This will eventually enter a local event loop.
+            self.debugMod.__dict__['__file__'] = self.running
+            sys.modules['__main__'] = self.debugMod
+            code = self.__compileFileSource(self.running)
+            if code:
+                sys.setprofile(self.callTraceEnabled)
+                res = self.mainThread.run(code, self.debugMod.__dict__)
+                self.progTerminated(res)
 
-            if cmd == DebugProtocol.RequestRun:
-                sys.argv = []
-                wd, fn, args = arg.split('|')
-                self.__setCoding(fn)
-                sys.argv.append(fn)
-                sys.argv.extend(eval(args.replace("u'", "'")))
-                sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
-                if wd == '':
-                    os.chdir(sys.path[1])
-                else:
-                    os.chdir(wd)
-
-                self.running = sys.argv[0]
-                self.mainFrame = None
-                self.botframe = None
-                self.inRawMode = False
-                
-                self.threads.clear()
-                self.attachThread(mainThread=True)
-                
-                # set the system exception handling function to ensure, that
-                # we report on all unhandled exceptions
-                sys.excepthook = self.__unhandled_exception
-                self.__interceptSignals()
-                
-                self.mainThread.tracePythonLibs(0)
-                
-                self.debugMod.__dict__['__file__'] = sys.argv[0]
-                sys.modules['__main__'] = self.debugMod
-                res = 0
-                code = self.__compileFileSource(self.running)
-                if code:
-                    try:
-                        exec(code, self.debugMod.__dict__)
-                    except SystemExit as exc:
-                        res = exc.code
-                        atexit._run_exitfuncs()
-                    self.writestream.flush()
-                    self.progTerminated(res)
-                return
-
-            if cmd == DebugProtocol.RequestProfile:
-                sys.setprofile(None)
-                import PyProfile
-                sys.argv = []
-                wd, fn, args, erase = arg.split('|')
-                self.__setCoding(fn)
-                sys.argv.append(fn)
-                sys.argv.extend(eval(args.replace("u'", "'")))
-                sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
-                if wd == '':
-                    os.chdir(sys.path[1])
-                else:
-                    os.chdir(wd)
-
-                # set the system exception handling function to ensure, that
-                # we report on all unhandled exceptions
-                sys.excepthook = self.__unhandled_exception
-                self.__interceptSignals()
-                
-                # generate a profile object
-                self.prof = PyProfile.PyProfile(sys.argv[0])
-                
-                if int(erase):
-                    self.prof.erase()
-                self.debugMod.__dict__['__file__'] = sys.argv[0]
-                sys.modules['__main__'] = self.debugMod
-                fp = open(sys.argv[0], encoding=self.__coding)
-                try:
-                    script = fp.read()
-                finally:
-                    fp.close()
-                if script:
-                    if not script.endswith('\n'):
-                        script += '\n'
-                    self.running = sys.argv[0]
-                    res = 0
-                    try:
-                        self.prof.run(script)
-                    except SystemExit as exc:
-                        res = exc.code
-                        atexit._run_exitfuncs()
-                    self.prof.save()
-                    self.writestream.flush()
-                    self.progTerminated(res)
-                return
-
-            if cmd == DebugProtocol.RequestCoverage:
-                from coverage import coverage
-                sys.argv = []
-                wd, fn, args, erase = arg.split('@@')
-                self.__setCoding(fn)
-                sys.argv.append(fn)
-                sys.argv.extend(eval(args.replace("u'", "'")))
-                sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
-                if wd == '':
-                    os.chdir(sys.path[1])
-                else:
-                    os.chdir(wd)
-                
-                # set the system exception handling function to ensure, that
-                # we report on all unhandled exceptions
-                sys.excepthook = self.__unhandled_exception
-                self.__interceptSignals()
-                
-                # generate a coverage object
-                self.cover = coverage(
-                    auto_data=True,
-                    data_file="{0}.coverage".format(
-                        os.path.splitext(sys.argv[0])[0]))
-                
-                if int(erase):
-                    self.cover.erase()
-                sys.modules['__main__'] = self.debugMod
-                self.debugMod.__dict__['__file__'] = sys.argv[0]
-                fp = open(sys.argv[0], encoding=self.__coding)
-                try:
-                    script = fp.read()
-                finally:
-                    fp.close()
-                if script:
-                    if not script.endswith('\n'):
-                        script += '\n'
-                    code = compile(script, sys.argv[0], 'exec')
-                    self.running = sys.argv[0]
-                    res = 0
-                    self.cover.start()
-                    try:
-                        exec(code, self.debugMod.__dict__)
-                    except SystemExit as exc:
-                        res = exc.code
-                        atexit._run_exitfuncs()
-                    self.cover.stop()
-                    self.cover.save()
-                    self.writestream.flush()
-                    self.progTerminated(res)
-                return
-
-            if cmd == DebugProtocol.RequestShutdown:
-                self.sessionClose()
-                return
-            
-            if cmd == DebugProtocol.RequestBreak:
-                fn, line, temporary, set, cond = arg.split('@@')
-                line = int(line)
-                set = int(set)
-                temporary = int(temporary)
+        elif method == "RequestRun":
+            sys.argv = []
+            self.__setCoding(params["filename"])
+            sys.argv.append(params["filename"])
+            sys.argv.extend(params["argv"])
+            sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
+            if params["workdir"] == '':
+                os.chdir(sys.path[1])
+            else:
+                os.chdir(params["workdir"])
 
-                if set:
-                    if cond == 'None' or cond == '':
-                        cond = None
-                    else:
-                        try:
-                            cond = compile(cond, '<string>', 'eval')
-                        except SyntaxError:
-                            self.write('{0}{1},{2:d}\n'.format(
-                                       DebugProtocol.ResponseBPConditionError,
-                                       fn, line))
-                            return
-                    Breakpoint(fn, line, temporary, cond)
-                else:
-                    Breakpoint.clear_break(fn, line)
-
-                return
+            self.running = sys.argv[0]
+            self.mainFrame = None
+            self.botframe = None
+            
+            self.fork_auto = params["autofork"]
+            self.fork_child = params["forkChild"]
             
-            if cmd == DebugProtocol.RequestBreakEnable:
-                fn, line, enable = arg.split(',')
-                line = int(line)
-                enable = int(enable)
-                
-                bp = Breakpoint.get_break(fn, line)
-                if bp is not None:
-                    if enable:
-                        bp.enable()
-                    else:
-                        bp.disable()
-                
-                return
+            self.threads.clear()
+            self.attachThread(mainThread=True)
+            
+            # set the system exception handling function to ensure, that
+            # we report on all unhandled exceptions
+            sys.excepthook = self.__unhandled_exception
+            self.__interceptSignals()
+            
+            self.mainThread.tracePythonLibs(False)
             
-            if cmd == DebugProtocol.RequestBreakIgnore:
-                fn, line, count = arg.split(',')
-                line = int(line)
-                count = int(count)
-                
-                bp = Breakpoint.get_break(fn, line)
-                if bp is not None:
-                    bp.ignore = count
-                
-                return
+            self.debugMod.__dict__['__file__'] = sys.argv[0]
+            sys.modules['__main__'] = self.debugMod
+            res = 0
+            code = self.__compileFileSource(self.running)
+            if code:
+                try:
+                    exec(code, self.debugMod.__dict__)
+                except SystemExit as exc:
+                    res = exc.code
+                    atexit._run_exitfuncs()
+                self.writestream.flush()
+                self.progTerminated(res)
+
+        elif method == "RequestCoverage":
+            from coverage import coverage
+            sys.argv = []
+            self.__setCoding(params["filename"])
+            sys.argv.append(params["filename"])
+            sys.argv.extend(params["argv"])
+            sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
+            if params["workdir"] == '':
+                os.chdir(sys.path[1])
+            else:
+                os.chdir(params["workdir"])
             
-            if cmd == DebugProtocol.RequestWatch:
-                cond, temporary, set = arg.split('@@')
-                set = int(set)
-                temporary = int(temporary)
-                
-                if cond.endswith(('??created??', '??changed??')):
-                    compiledCond, flag = cond.split()
-                else:
-                    compiledCond = cond
-                    flag = ''
-                
-                try:
-                    compiledCond = compile(compiledCond, '<string>', 'eval')
-                except SyntaxError:
-                    self.write('{0}{1}\n'.format(
-                        DebugProtocol.ResponseWPConditionError, cond))
-                    return
-                
-                if set:
-                    Watch(cond, compiledCond, flag, temporary)
-                else:
-                    Watch.clear_watch(cond)
-
-                return
+            # set the system exception handling function to ensure, that
+            # we report on all unhandled exceptions
+            sys.excepthook = self.__unhandled_exception
+            self.__interceptSignals()
+            
+            # generate a coverage object
+            self.cover = coverage(
+                auto_data=True,
+                data_file="{0}.coverage".format(
+                    os.path.splitext(sys.argv[0])[0]))
             
-            if cmd == DebugProtocol.RequestWatchEnable:
-                cond, enable = arg.split(',')
-                enable = int(enable)
-                
-                bp = Watch.get_watch(cond)
-                if bp is not None:
-                    if enable:
-                        bp.enable()
-                    else:
-                        bp.disable()
-                
-                return
-            
-            if cmd == DebugProtocol.RequestWatchIgnore:
-                cond, count = arg.split(',')
-                count = int(count)
-                
-                bp = Watch.get_watch(cond)
-                if bp is not None:
-                    bp.ignore = count
-                
-                return
-            
-            if cmd == DebugProtocol.RequestEval:
+            if params["erase"]:
+                self.cover.erase()
+            sys.modules['__main__'] = self.debugMod
+            self.debugMod.__dict__['__file__'] = sys.argv[0]
+            fp = open(sys.argv[0], encoding=self.__coding)
+            try:
+                script = fp.read()
+            finally:
+                fp.close()
+            if script:
+                if not script.endswith('\n'):
+                    script += '\n'
+                code = compile(script, sys.argv[0], 'exec')
+                self.running = sys.argv[0]
+                res = 0
+                self.cover.start()
                 try:
-                    value = eval(
-                        arg,
-                        self.currentThread.getCurrentFrame().f_globals,
-                        self.currentThread.getFrameLocals(self.framenr))
-                    self.currentThread.storeFrameLocals(self.framenr)
-                except Exception:
-                    # Report the exception and the traceback
-                    try:
-                        type, value, tb = sys.exc_info()
-                        sys.last_type = type
-                        sys.last_value = value
-                        sys.last_traceback = tb
-                        tblist = traceback.extract_tb(tb)
-                        del tblist[:1]
-                        list = traceback.format_list(tblist)
-                        if list:
-                            list.insert(0, "Traceback (innermost last):\n")
-                            list[len(list):] = \
-                                traceback.format_exception_only(type, value)
-                    finally:
-                        tblist = tb = None
+                    exec(code, self.debugMod.__dict__)
+                except SystemExit as exc:
+                    res = exc.code
+                    atexit._run_exitfuncs()
+                self.cover.stop()
+                self.cover.save()
+                self.writestream.flush()
+                self.progTerminated(res)
+        
+        elif method == "RequestProfile":
+            sys.setprofile(None)
+            import PyProfile
+            sys.argv = []
+            self.__setCoding(params["filename"])
+            sys.argv.append(params["filename"])
+            sys.argv.extend(params["argv"])
+            sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
+            if params["workdir"] == '':
+                os.chdir(sys.path[1])
+            else:
+                os.chdir(params["workdir"])
 
-                    for l in list:
-                        self.write(l)
+            # set the system exception handling function to ensure, that
+            # we report on all unhandled exceptions
+            sys.excepthook = self.__unhandled_exception
+            self.__interceptSignals()
+            
+            # generate a profile object
+            self.prof = PyProfile.PyProfile(sys.argv[0])
+            
+            if params["erase"]:
+                self.prof.erase()
+            self.debugMod.__dict__['__file__'] = sys.argv[0]
+            sys.modules['__main__'] = self.debugMod
+            fp = open(sys.argv[0], encoding=self.__coding)
+            try:
+                script = fp.read()
+            finally:
+                fp.close()
+            if script:
+                if not script.endswith('\n'):
+                    script += '\n'
+                self.running = sys.argv[0]
+                res = 0
+                try:
+                    self.prof.run(script)
+                except SystemExit as exc:
+                    res = exc.code
+                    atexit._run_exitfuncs()
+                self.prof.save()
+                self.writestream.flush()
+                self.progTerminated(res)
+        
+        elif method == "ExecuteStatement":
+            if self.buffer:
+                self.buffer = self.buffer + '\n' + params["statement"]
+            else:
+                self.buffer = params["statement"]
 
-                    self.write(DebugProtocol.ResponseException + '\n')
-                
+            try:
+                code = self.compile_command(self.buffer, self.readstream.name)
+            except (OverflowError, SyntaxError, ValueError):
+                # Report the exception
+                sys.last_type, sys.last_value, sys.last_traceback = \
+                    sys.exc_info()
+                self.sendJsonCommand("ClientOutput", {
+                    "text": "".join(traceback.format_exception_only(
+                        sys.last_type, sys.last_value))
+                })
+                self.buffer = ''
+            else:
+                if code is None:
+                    self.sendJsonCommand("ResponseContinue", {})
+                    return
                 else:
-                    self.write(str(value) + '\n')
-                    self.write(DebugProtocol.ResponseOK + '\n')
-                
-                return
-            
-            if cmd == DebugProtocol.RequestExec:
-                _globals = self.currentThread.getCurrentFrame().f_globals
-                _locals = self.currentThread.getFrameLocals(self.framenr)
-                try:
-                    code = compile(arg + '\n', '<stdin>', 'single')
-                    exec(code, _globals, _locals)
-                    self.currentThread.storeFrameLocals(self.framenr)
-                except Exception:
-                    # Report the exception and the traceback
+                    self.buffer = ''
+
                     try:
-                        type, value, tb = sys.exc_info()
-                        sys.last_type = type
-                        sys.last_value = value
-                        sys.last_traceback = tb
-                        tblist = traceback.extract_tb(tb)
-                        del tblist[:1]
-                        list = traceback.format_list(tblist)
-                        if list:
-                            list.insert(0, "Traceback (innermost last):\n")
-                            list[len(list):] = \
-                                traceback.format_exception_only(type, value)
-                    finally:
-                        tblist = tb = None
-
-                    for l in list:
-                        self.write(l)
-
-                    self.write(DebugProtocol.ResponseException + '\n')
-                
-                return
-            
-            if cmd == DebugProtocol.RequestBanner:
-                self.write('{0}{1}\n'.format(DebugProtocol.ResponseBanner,
-                           str(("Python {0}".format(sys.version),
-                                socket.gethostname(), self.variant))))
-                return
-            
-            if cmd == DebugProtocol.RequestCapabilities:
-                self.write('{0}{1:d}, "Python3"\n'.format(
-                    DebugProtocol.ResponseCapabilities,
-                    self.__clientCapabilities()))
-                return
-            
-            if cmd == DebugProtocol.RequestCompletion:
-                self.__completionList(arg.replace("u'", "'"))
-                return
-            
-            if cmd == DebugProtocol.RequestSetFilter:
-                scope, filterString = eval(arg.replace("u'", "'"))
-                self.__generateFilterObjects(int(scope), filterString)
-                return
-            
-            if cmd == DebugProtocol.RequestUTPrepare:
-                fn, tn, tfn, failed, cov, covname, erase = arg.split('|')
-                sys.path.insert(0, os.path.dirname(os.path.abspath(fn)))
-                os.chdir(sys.path[0])
-                failed = eval(failed)
-
-                # set the system exception handling function to ensure, that
-                # we report on all unhandled exceptions
-                sys.excepthook = self.__unhandled_exception
-                self.__interceptSignals()
-                
-                try:
-                    import unittest
-                    utModule = imp.load_source(tn, fn)
-                    try:
-                        if failed:
-                            self.test = unittest.defaultTestLoader\
-                                .loadTestsFromNames(failed, utModule)
+                        if self.running is None:
+                            exec(code, self.debugMod.__dict__)
                         else:
-                            self.test = unittest.defaultTestLoader\
-                                .loadTestsFromName(tfn, utModule)
-                    except AttributeError:
-                        self.test = unittest.defaultTestLoader\
-                            .loadTestsFromModule(utModule)
-                except Exception:
-                    exc_type, exc_value, exc_tb = sys.exc_info()
-                    self.write('{0}{1}\n'.format(
-                        DebugProtocol.ResponseUTPrepared,
-                        str((0, str(exc_type), str(exc_value)))))
-                    self.__exceptionRaised()
-                    return
-                
-                # generate a coverage object
-                if int(cov):
-                    from coverage import coverage
-                    self.cover = coverage(
-                        auto_data=True,
-                        data_file="{0}.coverage".format(
-                            os.path.splitext(covname)[0]))
-                    if int(erase):
-                        self.cover.erase()
-                else:
-                    self.cover = None
-                
-                self.write('{0}{1}\n'.format(
-                    DebugProtocol.ResponseUTPrepared,
-                    str((self.test.countTestCases(), "", ""))))
-                return
-            
-            if cmd == DebugProtocol.RequestUTRun:
-                from DCTestResult import DCTestResult
-                self.testResult = DCTestResult(self)
-                if self.cover:
-                    self.cover.start()
-                self.test.run(self.testResult)
-                if self.cover:
-                    self.cover.stop()
-                    self.cover.save()
-                self.write('{0}\n'.format(DebugProtocol.ResponseUTFinished))
-                return
-            
-            if cmd == DebugProtocol.RequestUTStop:
-                self.testResult.stop()
-                return
-        
-            if cmd == DebugProtocol.ResponseForkTo:
-                # this results from a separate event loop
-                self.fork_child = (arg == 'child')
-                self.eventExit = True
-                return
-            
-            if cmd == DebugProtocol.RequestForkMode:
-                self.fork_auto, self.fork_child = eval(arg)
-                return
-        
-        # If we are handling raw mode input then reset the mode and break out
-        # of the current event loop.
-        if self.inRawMode:
-            self.inRawMode = False
-            self.rawLine = line
-            self.eventExit = True
-            return
-
-        if self.buffer:
-            self.buffer = self.buffer + '\n' + line
-        else:
-            self.buffer = line
-
-        try:
-            code = self.compile_command(self.buffer, self.readstream.name)
-        except (OverflowError, SyntaxError, ValueError):
-            # Report the exception
-            sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
-            for l in traceback.format_exception_only(
-                    sys.last_type, sys.last_value):
-                self.write(l)
-            self.buffer = ''
-        else:
-            if code is None:
-                self.pendingResponse = DebugProtocol.ResponseContinue
-            else:
-                self.buffer = ''
-
-                try:
-                    if self.running is None:
-                        exec(code, self.debugMod.__dict__)
-                    else:
-                        if self.currentThread is None:
-                            # program has terminated
-                            self.running = None
-                            _globals = self.debugMod.__dict__
-                            _locals = _globals
-                        else:
-                            cf = self.currentThread.getCurrentFrame()
-                            # program has terminated
-                            if cf is None:
+                            if self.currentThread is None:
+                                # program has terminated
                                 self.running = None
                                 _globals = self.debugMod.__dict__
                                 _locals = _globals
                             else:
-                                frmnr = self.framenr
-                                while cf is not None and frmnr > 0:
-                                    cf = cf.f_back
-                                    frmnr -= 1
-                                _globals = cf.f_globals
-                                _locals = \
-                                    self.currentThread.getFrameLocals(
-                                        self.framenr)
-                        # reset sys.stdout to our redirector (unconditionally)
-                        if "sys" in _globals:
-                            __stdout = _globals["sys"].stdout
-                            _globals["sys"].stdout = self.writestream
-                            exec(code, _globals, _locals)
-                            _globals["sys"].stdout = __stdout
-                        elif "sys" in _locals:
-                            __stdout = _locals["sys"].stdout
-                            _locals["sys"].stdout = self.writestream
-                            exec(code, _globals, _locals)
-                            _locals["sys"].stdout = __stdout
-                        else:
-                            exec(code, _globals, _locals)
-                        
-                        self.currentThread.storeFrameLocals(self.framenr)
-                except SystemExit as exc:
-                    self.progTerminated(exc.code)
-                except Exception:
-                    # Report the exception and the traceback
+                                cf = self.currentThread.getCurrentFrame()
+                                # program has terminated
+                                if cf is None:
+                                    self.running = None
+                                    _globals = self.debugMod.__dict__
+                                    _locals = _globals
+                                else:
+                                    frmnr = self.framenr
+                                    while cf is not None and frmnr > 0:
+                                        cf = cf.f_back
+                                        frmnr -= 1
+                                    _globals = cf.f_globals
+                                    _locals = \
+                                        self.currentThread.getFrameLocals(
+                                            self.framenr)
+                            # reset sys.stdout to our redirector
+                            # (unconditionally)
+                            if "sys" in _globals:
+                                __stdout = _globals["sys"].stdout
+                                _globals["sys"].stdout = self.writestream
+                                exec(code, _globals, _locals)
+                                _globals["sys"].stdout = __stdout
+                            elif "sys" in _locals:
+                                __stdout = _locals["sys"].stdout
+                                _locals["sys"].stdout = self.writestream
+                                exec(code, _globals, _locals)
+                                _locals["sys"].stdout = __stdout
+                            else:
+                                exec(code, _globals, _locals)
+                            
+                            self.currentThread.storeFrameLocals(self.framenr)
+                    except SystemExit as exc:
+                        self.progTerminated(exc.code)
+                    except Exception:
+                        # Report the exception and the traceback
+                        tlist = []
+                        try:
+                            exc_type, exc_value, exc_tb = sys.exc_info()
+                            sys.last_type = exc_type
+                            sys.last_value = exc_value
+                            sys.last_traceback = exc_tb
+                            tblist = traceback.extract_tb(exc_tb)
+                            del tblist[:1]
+                            tlist = traceback.format_list(tblist)
+                            if tlist:
+                                tlist.insert(
+                                    0, "Traceback (innermost last):\n")
+                                tlist.extend(traceback.format_exception_only(
+                                    exc_type, exc_value))
+                        finally:
+                            tblist = exc_tb = None
+
+                        self.sendJsonCommand("ClientOutput", {
+                            "text": "".join(tlist)
+                        })
+            
+            self.sendJsonCommand("ResponseOK", {})
+        
+        elif method == "RequestStep":
+            self.currentThread.step(True)
+            self.eventExit = True
+
+        elif method == "RequestStepOver":
+            self.currentThread.step(False)
+            self.eventExit = True
+        
+        elif method == "RequestStepOut":
+            self.currentThread.stepOut()
+            self.eventExit = True
+        
+        elif method == "RequestStepQuit":
+            if self.passive:
+                self.progTerminated(42)
+            else:
+                self.set_quit()
+                self.eventExit = True
+        
+        elif method == "RequestContinue":
+            self.currentThread.go(params["special"])
+            self.eventExit = True
+        
+        elif method == "RawInput":
+            # If we are handling raw mode input then break out of the current
+            # event loop.
+            self.rawLine = params["input"]
+            self.eventExit = True
+        
+        elif method == "RequestBreakpoint":
+            if params["setBreakpoint"]:
+                if params["condition"] in ['None', '']:
+                    cond = None
+                elif params["condition"] is not None:
                     try:
-                        exc_type, exc_value, exc_tb = sys.exc_info()
-                        sys.last_type = exc_type
-                        sys.last_value = exc_value
-                        sys.last_traceback = exc_tb
-                        tblist = traceback.extract_tb(exc_tb)
-                        del tblist[:1]
-                        list = traceback.format_list(tblist)
-                        if list:
-                            list.insert(0, "Traceback (innermost last):\n")
-                            list[len(list):] = traceback.format_exception_only(
-                                exc_type, exc_value)
-                    finally:
-                        tblist = exc_tb = None
-
-                    for l in list:
-                        self.write(l)
-
+                        cond = compile(params["condition"], '<string>', 'eval')
+                    except SyntaxError:
+                        self.sendJsonCommand("ResponseBPConditionError", {
+                            "filename": params["filename"],
+                            "line": params["line"],
+                        })
+                        return
+                else:
+                    cond = None
+                
+                Breakpoint(
+                    params["filename"], params["line"], params["temporary"],
+                    cond)
+            else:
+                Breakpoint.clear_break(params["filename"], params["line"])
+        
+        elif method == "RequestBreakpointEnable":
+            bp = Breakpoint.get_break(params["filename"], params["line"])
+            if bp is not None:
+                if params["enable"]:
+                    bp.enable()
+                else:
+                    bp.disable()
+        
+        elif method == "RequestBreakpointIgnore":
+            bp = Breakpoint.get_break(params["filename"], params["line"])
+            if bp is not None:
+                bp.ignore = params["count"]
+        
+        elif method == "RequestWatch":
+            if params["setWatch"]:
+                if params["condition"].endswith(
+                        ('??created??', '??changed??')):
+                    compiledCond, flag = params["condition"].split()
+                else:
+                    compiledCond = params["condition"]
+                    flag = ''
+                
+                try:
+                    compiledCond = compile(
+                        compiledCond, '<string>', 'eval')
+                except SyntaxError:
+                    self.sendJsonCommand("ResponseWatchConditionError", {
+                        "condition": params["condition"],
+                    })
+                    return
+                Watch(
+                    params["condition"], compiledCond, flag,
+                    params["temporary"])
+            else:
+                Watch.clear_watch(params["condition"])
+        
+        elif method == "RequestWatchEnable":
+            wp = Watch.get_watch(params["condition"])
+            if wp is not None:
+                if params["enable"]:
+                    wp.enable()
+                else:
+                    wp.disable()
+        
+        elif method == "RequestWatchIgnore":
+            wp = Watch.get_watch(params["condition"])
+            if wp is not None:
+                wp.ignore = params["count"]
+        
+        elif method == "RequestShutdown":
+            self.sessionClose()
+        
+        elif method == "RequestCompletion":
+            self.__completionList(params["text"])
+        
+        elif method == "RequestUTPrepare":
+            sys.path.insert(
+                0, os.path.dirname(os.path.abspath(params["filename"])))
+            os.chdir(sys.path[0])
+            
+            # set the system exception handling function to ensure, that
+            # we report on all unhandled exceptions
+            sys.excepthook = self.__unhandled_exception
+            self.__interceptSignals()
+            
+            try:
+                import unittest
+                utModule = imp.load_source(
+                    params["testname"], params["filename"])
+                try:
+                    if params["failed"]:
+                        self.test = unittest.defaultTestLoader\
+                            .loadTestsFromNames(params["failed"], utModule)
+                    else:
+                        self.test = unittest.defaultTestLoader\
+                            .loadTestsFromName(params["testfunctionname"],
+                                               utModule)
+                except AttributeError:
+                    self.test = unittest.defaultTestLoader\
+                        .loadTestsFromModule(utModule)
+            except Exception:
+                exc_type, exc_value, exc_tb = sys.exc_info()
+                self.sendJsonCommand("ResponseUTPrepared", {
+                    "count": 0,
+                    "exception": exc_type.__name__,
+                    "message": str(exc_value),
+                })
+                return
+            
+            # generate a coverage object
+            if params["coverage"]:
+                from coverage import coverage
+                self.cover = coverage(
+                    auto_data=True,
+                    data_file="{0}.coverage".format(
+                        os.path.splitext(params["coveragefile"])[0]))
+                if params["coverageerase"]:
+                    self.cover.erase()
+            else:
+                self.cover = None
+            
+            self.sendJsonCommand("ResponseUTPrepared", {
+                "count": self.test.countTestCases(),
+                "exception": "",
+                "message": "",
+            })
+        
+        elif method == "RequestUTRun":
+            from DCTestResult import DCTestResult
+            self.testResult = DCTestResult(self)
+            if self.cover:
+                self.cover.start()
+            self.test.run(self.testResult)
+            if self.cover:
+                self.cover.stop()
+                self.cover.save()
+            self.sendJsonCommand("ResponseUTFinished", {})
+        
+        elif method == "RequestUTStop":
+            self.testResult.stop()
+        
+        elif method == "ResponseForkTo":
+            # this results from a separate event loop
+            self.fork_child = (params["target"] == 'child')
+            self.eventExit = True
+    
+    def sendJsonCommand(self, method, params):
+        """
+        Public method to send a single command or response to the IDE.
+        
+        @param method command or response command name to be sent
+        @type str
+        @param params dictionary of named parameters for the command or
+            response
+        @type dict
+        """
+        cmd = prepareJsonCommand(method, params)
+        
+        self.writestream.write_p(cmd)
+        self.writestream.flush()
+    
+    def sendClearTemporaryBreakpoint(self, filename, lineno):
+        """
+        Public method to signal the deletion of a temporary breakpoint.
+        
+        @param filename name of the file the bp belongs to
+        @type str
+        @param lineno linenumber of the bp
+        @type int
+        """
+        self.sendJsonCommand("ResponseClearBreakpoint", {
+            "filename": filename,
+            "line": lineno
+        })
+    
+    def sendClearTemporaryWatch(self, condition):
+        """
+        Public method to signal the deletion of a temporary watch expression.
+        
+        @param condition condition of the watch expression to be cleared
+        @type str
+        """
+        self.sendJsonCommand("ResponseClearWatch", {
+            "condition": condition,
+        })
+    
+    def sendResponseLine(self, stack):
+        """
+        Public method to send the current call stack.
+        
+        @param stack call stack
+        @type list
+        """
+        self.sendJsonCommand("ResponseLine", {
+            "stack": stack,
+        })
+    
+    def sendCallTrace(self, event, fromInfo, toInfo):
+        """
+        Public method to send a call trace entry.
+        
+        @param event trace event (call or return)
+        @type str
+        @param fromInfo dictionary containing the origin info
+        @type dict with 'filename', 'linenumber' and 'codename'
+            as keys
+        @param toInfo dictionary containing the target info
+        @type dict with 'filename', 'linenumber' and 'codename'
+            as keys
+        """
+        self.sendJsonCommand("CallTrace", {
+            "event": event[0],
+            "from": fromInfo,
+            "to": toInfo,
+        })
+    
+    def sendException(self, exceptionType, exceptionMessage, stack):
+        """
+        Public method to send information for an exception.
+        
+        @param exceptionType type of exception raised
+        @type str
+        @param exceptionMessage message of the exception
+        @type str
+        @param stack stack trace information
+        @type list
+        """
+        self.sendJsonCommand("ResponseException", {
+            "type": exceptionType,
+            "message": exceptionMessage,
+            "stack": stack,
+        })
+    
+    def sendSyntaxError(self, message, filename, lineno, charno):
+        """
+        Public method to send information for a syntax error.
+        
+        @param message syntax error message
+        @type str
+        @param filename name of the faulty file
+        @type str
+        @param lineno line number info
+        @type int
+        @param charno character number info
+        @type int
+        """
+        self.sendJsonCommand("ResponseSyntax", {
+            "message": message,
+            "filename": filename,
+            "linenumber": lineno,
+            "characternumber": charno,
+        })
+    
+    def sendPassiveStartup(self, filename, exceptions):
+        """
+        Public method to send the passive start information.
+        
+        @param filename name of the script
+        @type str
+        @param exceptions flag to enable exception reporting of the IDE
+        @type bool
+        """
+        self.sendJsonCommand("PassiveStartup", {
+            "filename": filename,
+            "exceptions": exceptions,
+        })
+    
     def __clientCapabilities(self):
         """
         Private method to determine the clients capabilities.
@@ -1042,23 +1037,48 @@
             return (
                 self.clientCapabilities & ~DebugClientCapabilities.HasProfiler)
     
-    def write(self, s):
+    def readReady(self, stream):
         """
-        Public method to write data to the output stream.
+        Public method called when there is data ready to be read.
         
-        @param s data to be written (string)
+        @param stream file like object that has data to be written
         """
-        self.writestream.write(s)
-        self.writestream.flush()
+        try:
+            got = stream.readline_p()
+        except Exception:
+            return
+
+        if len(got) == 0:
+            self.sessionClose()
+            return
+
+        self.__receiveBuffer = self.__receiveBuffer + got
 
+        # Call handleLine for the line if it is complete.
+        eol = self.__receiveBuffer.find('\n')
+        while eol >= 0:
+            line = self.__receiveBuffer[:eol + 1]
+            self.__receiveBuffer = self.__receiveBuffer[eol + 1:]
+            self.handleLine(line)
+            eol = self.__receiveBuffer.find('\n')
+
+    def writeReady(self, stream):
+        """
+        Public method called when we are ready to write data.
+        
+        @param stream file like object that has data to be written
+        """
+        stream.write_p("")
+        stream.flush()
+    
     def __interact(self):
         """
         Private method to interact with the debugger.
         """
         global DebugClientInstance
 
-        self.setDescriptors(self.readstream, self.writestream)
         DebugClientInstance = self
+        self.__receiveBuffer = ""
 
         if not self.passive:
             # At this point simulate an event loop.
@@ -1093,13 +1113,13 @@
                 continue
 
             if self.readstream in rrdy:
-                self.readReady(self.readstream.fileno())
+                self.readReady(self.readstream)
 
             if self.writestream in wrdy:
-                self.writeReady(self.writestream.fileno())
+                self.writeReady(self.writestream)
 
             if self.errorstream in wrdy:
-                self.writeReady(self.errorstream.fileno())
+                self.writeReady(self.errorstream)
 
         self.eventExit = None
         self.pollingDisabled = False
@@ -1125,13 +1145,13 @@
             return
 
         if self.readstream in rrdy:
-            self.readReady(self.readstream.fileno())
+            self.readReady(self.readstream)
 
         if self.writestream in wrdy:
-            self.writeReady(self.writestream.fileno())
+            self.writeReady(self.writestream)
 
         if self.errorstream in wrdy:
-            self.writeReady(self.errorstream.fileno())
+            self.writeReady(self.errorstream)
     
     def connectDebugger(self, port, remoteAddress=None, redirect=True):
         """
@@ -1227,9 +1247,13 @@
         else:
             fargs = ""
         
-        siglist = [message, [filename, linenr, ffunc, fargs]]
-        
-        self.write("{0}{1}".format(DebugProtocol.ResponseSignal, str(siglist)))
+        self.sendJsonCommand("ResponseSignal", {
+            "message": message,
+            "filename": filename,
+            "linenumber": linenr,
+            "function": ffunc,
+            "arguments": fargs,
+        })
     
     def absPath(self, fn):
         """
@@ -1280,25 +1304,28 @@
         """
         return self.running
 
-    def progTerminated(self, status):
+    def progTerminated(self, status, message=""):
         """
         Public method to tell the debugger that the program has terminated.
         
         @param status return status
         @type int
+        @param message status message
+        @type str
         """
         if status is None:
             status = 0
-        else:
-            try:
-                int(status)
-            except ValueError:
-                status = 1
+        elif not isinstance(status, int):
+            message = str(status)
+            status = 1
 
         if self.running:
             self.set_quit()
             self.running = None
-            self.write('{0}{1:d}\n'.format(DebugProtocol.ResponseExit, status))
+            self.sendJsonCommand("ResponseExit", {
+                "status": status,
+                "message": message,
+            })
         
         # reset coding
         self.__coding = self.defaultCoding
@@ -1338,7 +1365,7 @@
         else:
             dict = f.f_locals
             
-        varlist = [scope]
+        varlist = []
         
         if scope != -1:
             keylist = dict.keys()
@@ -1346,8 +1373,10 @@
             vlist = self.__formatVariablesList(keylist, dict, scope, filter)
             varlist.extend(vlist)
         
-        self.write('{0}{1}\n'.format(
-            DebugProtocol.ResponseVariables, str(varlist)))
+        self.sendJsonCommand("ResponseVariables", {
+            "scope": scope,
+            "variables": varlist,
+        })
     
     def __dumpVariable(self, var, frmnr, scope, filter):
         """
@@ -1383,269 +1412,61 @@
         else:
             dict = f.f_locals
         
-        varlist = [scope, var]
+        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 = '["{0!s}"]'.format(var[i - 1])
-                                dict = odict
-                            else:
-                                access = '["{0!s}"]'.format(var[i][:-2])
-                        else:
-                            if var[i][:-2] == '...':
-                                if oaccess:
-                                    access = oaccess
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        access, var[i - 1])
-                                dict = odict
-                            else:
-                                if oaccess:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        oaccess, var[i][:-2])
-                                    oaccess = ''
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        access, var[i][:-2])
-                        if var[i][-2:] == "{}":     # __IGNORE_WARNING__
-                            isDict = True
-                        break
-                    else:
-                        if not access:
-                            if var[i][:-2] == '...':
-                                access = '["{0!s}"]'.format(var[i - 1])
-                                dict = odict
-                            else:
-                                access = '["{0!s}"]'.format(var[i][:-2])
-                        else:
-                            if var[i][:-2] == '...':
-                                access = '{0!s}[{1!s}]'.format(
-                                    access, var[i - 1])
-                                dict = odict
-                            else:
-                                if oaccess:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        oaccess, var[i][:-2])
-                                    oaccess = ''
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        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 = '{0!s}[{1!s}]'.format(oaccess, var[i])
-                        else:
-                            access = '{0!s}[{1!s}]'.format(access, var[i])
-                        if var[i - 1][:-2] == '...':
-                            oaccess = access
-                        else:
-                            oaccess = ''
-                        try:
-                            loc = {"dict": dict}
-                            exec('mdict = dict{0!s}.__dict__\nobj = dict{0!s}'
-                                 .format(access), globals(), loc)
-                            mdict = loc["mdict"]
-                            obj = loc["obj"]
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                            ndict.update(mdict)
-                        except Exception:
-                            pass
-                        try:
-                            loc = {"dict": dict}
-                            exec('mcdict = dict{0!s}.__class__.__dict__'
-                                 .format(access), globals(), loc)
-                            ndict.update(loc["mcdict"])
-                            if mdict and "sipThis" not in mdict.keys():
-                                del rvar[0:2]
-                                access = ""
-                        except Exception:
-                            pass
-                        try:
-                            loc = {"cdict": {}, "dict": dict}
-                            exec('slv = dict{0!s}.__slots__'.format(access),
-                                 globals(), loc)
-                            for v in loc["slv"]:
-                                try:
-                                    loc["v"] = v
-                                    exec('cdict[v] = dict{0!s}.{1!s}'.format(
-                                        access, v), globals, loc)
-                                except Exception:
-                                    pass
-                            ndict.update(loc["cdict"])
-                            exec('obj = dict{0!s}'.format(access),
-                                 globals(), loc)
-                            obj = loc["obj"]
-                            access = ""
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(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 = str(type(qvar))[1:-1].split()[1][1:-1]
-                        except Exception:
-                            pass
-                        try:
-                            slv = dict[var[i]].__slots__
-                            loc = {"cdict": {}, "dict": dict,
-                                   "var": var, "i": i}
-                            for v in slv:
-                                try:
-                                    loc["v"] = v
-                                    exec('cdict[v] = dict[var[i]].{0!s}'
-                                         .format(v),
-                                         globals(), loc)
-                                except Exception:
-                                    pass
-                            ndict.update(loc["cdict"])
-                            obj = dict[var[i]]
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(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:
-                    loc = {"udict": udict}
-                    exec('qvar = udict{0!s}'.format(access), globals(), loc)
-                    qvar = loc["qvar"]
-                # this has to be in line with VariablesViewer.indicators
-                elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]:   # __IGNORE_WARNING__
-                    loc = {"udict": udict}
-                    exec('qvar = udict["{0!s}"][{1!s}]'.format(rvar[0][:-2],
-                         rvar[1]),
-                         globals(), loc)
-                    qvar = loc["qvar"]
-                else:
-                    qvar = udict[var[-1]]
-                qvtype = str(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:
-                        loc = {"udict": udict}
-                        exec('qvar = udict{0!s}'.format(access),
-                             globals(), loc)
-                        qvar = loc["qvar"]
-                    # this has to be in line with VariablesViewer.indicators
-                    elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]:   # __IGNORE_WARNING__
-                        loc = {"udict": udict}
-                        exec('qvar = udict["{0!s}"][{1!s}]'.format(
-                             rvar[0][:-2], rvar[1]), globals(), loc)
-                        qvar = loc["qvar"]
-                    else:
-                        qvar = udict[var[-1]]
-                    qvtype = str(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:
-                            loc = {"dict": dict}
-                            exec("dict = dict{0!s}".format(access), globals(),
-                                 loc)
-                            dict = loc["dict"]
-                        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)
+                    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,
+            "variable": var,
+            "variables": varlist,
+        })
         
-            if obj is not None and not formatSequences:
-                try:
-                    if repr(obj).startswith('{'):
-                        varlist.append(
-                            ('...', 'dict', "{0:d}".format(len(obj.keys()))))
-                    elif repr(obj).startswith('['):
-                        varlist.append(
-                            ('...', 'list', "{0:d}".format(len(obj))))
-                    elif repr(obj).startswith('('):
-                        varlist.append(
-                            ('...', 'tuple', "{0:d}".format(len(obj))))
-                except Exception:
-                    pass
+    def __extractIndicators(self, var):
+        """
+        Private method to extract the indicator string from a variable text.
         
-        self.write('{0}{1}\n'.format(
-            DebugProtocol.ResponseVariable, str(varlist)))
+        @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
         
-    def __formatQtVariable(self, value, vtype):
+        return var, ""
+        
+    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 vtype type of the variable to be formatted (string)
+        @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", "{0}".format(chr(value.unicode()))))
@@ -1818,7 +1639,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)
@@ -1835,7 +1658,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:
@@ -1852,7 +1675,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:
@@ -1862,7 +1685,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 ['{', '(', '[']:
@@ -1929,8 +1753,10 @@
             pass
         self.__getCompletionList(text, self.complete, completions)
         
-        self.write("{0}{1}||{2}\n".format(DebugProtocol.ResponseCompletion,
-                                          str(list(completions)), text))
+        self.sendJsonCommand("ResponseCompletion", {
+            "completions": list(completions),
+            "text": text,
+        })
 
     def __getCompletionList(self, text, completer, completions):
         """
@@ -1988,15 +1814,13 @@
         if self.running:
             self.__setCoding(self.running)
         self.passive = True
-        self.write("{0}{1}|{2:d}\n".format(DebugProtocol.PassiveStartup,
-                                           self.running, exceptions))
+        self.sendPassiveStartup(self.running, exceptions)
         self.__interact()
         
         # setup the debugger variables
         self._fncache = {}
         self.dircache = []
         self.mainFrame = None
-        self.inRawMode = False
         self.debugging = True
         
         self.attachThread(mainThread=True)
@@ -2049,12 +1873,10 @@
         self.running = sys.argv[0]
         self.__setCoding(self.running)
         self.mainFrame = None
-        self.inRawMode = False
         self.debugging = True
         
         self.passive = True
-        self.write("{0}{1}|{2:d}\n".format(
-            DebugProtocol.PassiveStartup, self.running, exceptions))
+        self.sendPassiveStartup(self.running, exceptions)
         self.__interact()
         
         self.attachThread(mainThread=True)
@@ -2211,7 +2033,7 @@
         @return process ID (integer)
         """
         if not self.fork_auto:
-            self.write(DebugProtocol.RequestForkTo + '\n')
+            self.sendJsonCommand("RequestForkTo", {})
             self.eventLoop(True)
         pid = DebugClientOrigFork()
         if pid == 0:

eric ide

mercurial