# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2005 Detlev Offenbach <detlev@die-offenbachs.de>
# Copyright (c) 2000 Phil Thompson <phil@river-bank.demon.co.uk>
#

"""
Module implementing a debug client base class.
"""

import sys
import socket
import select
import codeop
import traceback
import os
import atexit
import time
import imp
import re


from DebugProtocol import *
import DebugClientCapabilities
from DebugBase import printerr
from AsyncFile import *
from Config import ConfigVarTypeStrings
from FlexCompleter import Completer


DebugClientInstance = None
debugging = 0

def DebugClientRawInput(prompt="", echo=1):
    """
    Replacement for the standard raw_input builtin.
    
    This function works with the split debugger.
    
    @param prompt The prompt to be shown. (string)
    @param echo Flag indicating echoing of the input (boolean)
    """
    if DebugClientInstance is None:
        return DebugClientOrigRawInput(prompt)

    return DebugClientInstance.raw_input(prompt, echo)

# Use our own raw_input().
try:
    DebugClientOrigRawInput = __builtins__.__dict__['raw_input']
    __builtins__.__dict__['raw_input'] = DebugClientRawInput
except:
    import __main__
    DebugClientOrigRawInput = __main__.__builtins__.__dict__['raw_input']
    __main__.__builtins__.__dict__['raw_input'] = DebugClientRawInput

def DebugClientInput(prompt=""):
    """
    Replacement for the standard input builtin.
    
    This function works with the split debugger.
    
    @param prompt The prompt to be shown. (string)
    """
    if DebugClientInstance is None:
        return DebugClientOrigInput(prompt)

    return DebugClientInstance.input(prompt)

# Use our own input().
try:
    DebugClientOrigInput = __builtins__.__dict__['input']
    __builtins__.__dict__['input'] = DebugClientInput
except:
    import __main__
    DebugClientOrigInput = __main__.__builtins__.__dict__['input']
    __main__.__builtins__.__dict__['input'] = DebugClientInput


class DebugClientBase:
    """
    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
    &gt;Load&lt;, &gt;Step&lt;, &gt;StepInto&lt;, ... 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 &gt;OK?&lt;.  If the statement was incomplete then the response
    is &gt;Continue&lt;.  If there was an exception then the response is &gt;Exception&lt;.
    Otherwise the response is &gt;OK&lt;.  The reason for the &gt;OK?&lt; part is to
    provide a sentinal (ie. the responding &gt;OK&lt;) 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.

    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
    being debugged closing or crashing.
    
    <b>Note</b>: This class is meant to be subclassed by individual DebugClient classes.
    Do not instantiate it directly.
    """
    clientCapabilities = DebugClientCapabilities.HasAll
    
    def __init__(self):
        """
        Constructor
        """
        self.breakpoints = {}

        # The next couple of members are needed for the threaded version.
        # For this base class they contain static values for the non threaded
        # debugger
        
        # dictionary of all threads running
        self.threads = {}
        
        # the "current" thread, basically the thread we are at a breakpoint for.
        self.currentThread = self
        
        # special objects representing the main scripts thread and frame
        self.mainThread = self
        self.mainFrame = None
        
        # The context to run the debugged program in.
        self.debugMod = imp.new_module('__main__')

        # The list of complete lines to execute.
        self.buffer = ''
        
        # The list of regexp objects to filter variables against
        self.globalsFilterObjects = []
        self.localsFilterObjects = []

        self.pendingResponse = ResponseOK
        self.fncache = {}
        self.dircache = []
        self.inRawMode = 0
        self.mainProcStr = None     # used for the passive mode
        self.passive = 0            # used to indicate the passive mode
        self.running = None
        self.test = None
        self.tracePython = 0

        self.readstream = None
        self.writestream = None
        self.errorstream = None
        
        # So much for portability.
        if sys.platform == 'win32':
            self.skipdir = sys.prefix
        else:
            self.skipdir = os.path.join(sys.prefix,'lib/python' + sys.version[0:3])
            
        self.variant = 'You should not see this'
        
        # commandline completion stuff
        self.complete = Completer(self.debugMod.__dict__).complete
        
        if sys.hexversion < 0x2020000:
            self.compile_command = codeop.compile_command
        else:
            self.compile_command = codeop.CommandCompiler()
        
        self.coding_re = re.compile(r"coding[:=]\s*([-\w_.]+)")
        self.defaultCoding = 'utf-8'

    def get_coding(self, filename):
        """
        Function to get the coding of a python file.
        
        @param filename name of the file to inspect (string)
        @return coding string (default utf-8)
        """
        default = 'latin-1'
        try:
            f = open(filename, 'rb')
            # read the first and second line
            text = f.readline()
            text = "%s%s" % (text, f.readline())
            f.close()
        except IOError:
            return default
        
        for l in text.splitlines():
            m = self.coding_re.search(l)
            if m:
                return m.group(1)
        return default

    def attachThread(self, target=None, args=None, kwargs=None, mainThread=0):
        """
        Public method to setup a thread for DebugClient to debug.
        
        If mainThread is non-zero, then we are attaching to the already 
        started mainthread of the app and the rest of the args are ignored.
        
        This is just an empty function and is overridden in the threaded
        debugger.
        
        @param target the start function of the target thread (i.e. the user code)
        @param args arguments to pass to target
        @param kwargs keyword arguments to pass to target
        @param mainThread non-zero, if we are attaching to the already 
              started mainthread of the app
        @return The identifier of the created thread
        """
        pass

    def raw_input(self,prompt,echo):
        """
        Public method to implement raw_input() using the event loop.
        
        @param prompt the prompt to be shown (string)
        @param echo Flag indicating echoing of the input (boolean)
        @return the entered string
        """
        self.write("%s%s\n" % (ResponseRaw, str((prompt, echo))))
        self.inRawMode = 1
        self.eventLoop()
        return self.rawLine

    def input(self,prompt):
        """
        Public method to implement input() using the event loop.
        
        @param prompt the prompt to be shown (string)
        @return the entered string evaluated as a Python expresion
        """
        return eval(self.raw_input(prompt, 1))
        
    def handleException(self):
        """
        Private method called in the case of an exception
        
        It ensures that the debug server is informed of the raised exception.
        """
        self.pendingResponse = ResponseException
    
    def sessionClose(self):
        """
        Private method to close the session with the debugger and terminate.
        """
        try:
            self.set_quit()
        except:
            pass

        # clean up asyncio.
        self.disconnect()
        
        # make sure we close down our end of the socket
        # might be overkill as normally stdin, stdout and stderr
        # SHOULD be closed on exit, but it does not hurt to do it here
        self.readstream.close(1)
        self.writestream.close(1)
        self.errorstream.close(1)

        # Ok, go away.
        sys.exit()

    def handleLine(self,line):
        """
        Private method to handle the receipt of a complete line.

        It first looks for a valid protocol token at the start of the line. Thereafter
        it trys to execute the lines accumulated so far.
        
        @param line the received line
        """
        global debugging
        
        # Remove any newline.
        if line[-1] == '\n':
            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 == RequestVariables:
                frmnr, scope, filter = eval(arg)
                self.dumpVariables(int(frmnr), int(scope), filter)
                return
                
            if cmd == RequestVariable:
                var, frmnr, scope, filter = eval(arg)
                self.dumpVariable(var, int(frmnr), int(scope), filter)
                return
                
            if cmd == RequestStep:
                self.currentThread.step(1)
                self.eventExit = 1
                return

            if cmd == RequestStepOver:
                self.currentThread.step(0)
                self.eventExit = 1
                return
                
            if cmd == RequestStepOut:
                self.currentThread.stepOut()
                self.eventExit = 1
                return
                
            if cmd == RequestStepQuit:
                if self.passive:
                    self.progTerminated(42)
                else:
                    self.set_quit()
                    self.eventExit = 1
                return

            if cmd == RequestContinue:
                special = int(arg)
                self.currentThread.go(special)
                self.eventExit = 1
                return

            if cmd == RequestOK:
                self.write(self.pendingResponse + '\n')
                self.pendingResponse = ResponseOK
                return

            if cmd == RequestEnv:
                env = eval(arg)
                for key, value in env.items():
                    os.environ[key] = value
                return

            if cmd == RequestLoad:
                debugging = 1
                self.fncache = {}
                self.dircache = []
                sys.argv = []
                wd, fn, args, tracePython = arg.split('|')
                coding = self.get_coding(fn)
                try:
                    sys.setappdefaultencoding(coding)
                except AttributeError:
                    pass
                sys.argv.append(fn)
                sys.argv.extend(eval(args))
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)
                tracePython = int(tracePython)
                self.running = sys.argv[0]
                self.mainFrame = None
                self.inRawMode = 0
                
                self.threads.clear()
                self.attachThread(mainThread = 1)
                
                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # clear all old breakpoints, they'll get set after we have started
                self.mainThread.clear_all_breaks()
                
                self.mainThread.tracePython = 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
                res = self.mainThread.run('execfile(' + `self.running` + ')',self.debugMod.__dict__)
                self.progTerminated(res)
                return

            if cmd == RequestRun:
                debugging = 0
                sys.argv = []
                wd, fn, args = arg.split('|')
                coding = self.get_coding(fn)
                try:
                    sys.setappdefaultencoding(coding)
                except AttributeError:
                    pass
                sys.argv.append(fn)
                sys.argv.extend(eval(args))
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                self.running = sys.argv[0]
                self.mainFrame = None
                self.botframe = None
                self.inRawMode = 0
                
                self.threads.clear()
                self.attachThread(mainThread = 1)
                
                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                self.mainThread.tracePython = 0 
                
                self.debugMod.__dict__['__file__'] = sys.argv[0]
                sys.modules['__main__'] = self.debugMod
                execfile(sys.argv[0], self.debugMod.__dict__)
                return

            if cmd == RequestCoverage:
                debugging = 0
                import PyCoverage
                sys.argv = []
                wd, fn, args, erase, exclude = arg.split('@@')
                coding = self.get_coding(fn)
                try:
                    sys.setappdefaultencoding(coding)
                except AttributeError:
                    pass
                sys.argv.append(fn)
                sys.argv.extend(eval(args))
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)
                
                if not exclude:
                    # use a default exclude pattern
                    exclude = "#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]"

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # generate a coverage object
                self.cover = PyCoverage.coverage(sys.argv[0])
                
                # register an exit handler to save the collected data
                atexit.register(self.cover.save)

                if int(erase):
                    self.cover.erase()
                self.cover.exclude(exclude, 1)
                sys.modules['__main__'] = self.debugMod
                self.cover.start()
                self.debugMod.__dict__['__file__'] = sys.argv[0]
                execfile(sys.argv[0], self.debugMod.__dict__)
                self.cover.save()
                return

            if cmd == RequestProfile:
                debugging = 0
                import PyProfile
                sys.argv = []
                wd, fn, args, erase = arg.split('|')
                coding = self.get_coding(fn)
                try:
                    sys.setappdefaultencoding(coding)
                except AttributeError:
                    pass
                sys.argv.append(fn)
                sys.argv.extend(eval(args))
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # 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
                self.prof.run('execfile(' + `sys.argv[0]` + ')')
                self.prof.save()
                return

            if cmd == RequestCyclops:
                debugging = 0
                from Cyclops import RunCyclops
                sys.argv = []
                wd, fn, args, modfunc, reports = arg.split('|')
                coding = self.get_coding(fn)
                try:
                    sys.setappdefaultencoding(coding)
                except AttributeError:
                    pass
                sys.argv.append(fn)
                sys.argv.extend(eval(args))
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                sys.modules['__main__'] = self.debugMod
                RunCyclops.run(fn, modfunc, int(reports), self)
                return

            if cmd == RequestShutdown:
                self.sessionClose()
                return
                
            if cmd == RequestBreak:
                fn, line, temporary, set, cond = arg.split(',')
                line = int(line)
                set = int(set)
                temporary = int(temporary)

                if set:
                    if cond == 'None':
                        cond = None
                    self.mainThread.set_break(fn, line, temporary, cond)
                else:
                    self.mainThread.clear_break(fn, line)

                return
                
            if cmd == RequestBreakEnable:
                fn, line, enable = arg.split(',')
                line = int(line)
                enable = int(enable)
                
                bp = self.mainThread.get_break(fn, line)
                if bp is not None:
                    if enable:
                        bp.enable()
                    else:
                        bp.disable()
                    
                return
                
            if cmd == RequestBreakIgnore:
                fn, line, count = arg.split(',')
                line = int(line)
                count = int(count)
                
                bp = self.mainThread.get_break(fn, line)
                if bp is not None:
                    bp.ignore = count
                    
                return
                
            if cmd == RequestWatch:
                cond, temporary, set = arg.split(',')
                set = int(set)
                temporary = int(temporary)

                if set:
                    self.mainThread.set_watch(cond, temporary)
                else:
                    self.mainThread.clear_watch(cond)

                return
                
            if cmd == RequestWatchEnable:
                cond, enable = arg.split(',')
                enable = int(enable)
                
                bp = self.mainThread.get_watch(cond)
                if bp is not None:
                    if enable:
                        bp.enable()
                    else:
                        bp.disable()
                    
                return
                
            if cmd == RequestWatchIgnore:
                cond, count = arg.split(',')
                count = int(count)
                
                bp = self.mainThread.get_watch(cond)
                if bp is not None:
                    bp.ignore = count
                    
                return
                
            if cmd == RequestEval:
                try:
                    value = eval(arg, self.currentThread.getCurrentFrame().f_globals,
                                      self.currentThread.getCurrentFrame().f_locals)
                except:
                    # 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

                    map(self.write,list)

                    self.write(ResponseException + '\n')
                    
                else:
                    self.write(str(value) + '\n')
                    self.write(ResponseOK + '\n')
                    
                return
            
            if cmd == RequestExec:
                _globals = self.currentThread.getCurrentFrame().f_globals
                _locals = self.currentThread.getCurrentFrame().f_locals
                try:
                    code = compile(arg + '\n', '<stdin>', 'single')
                    exec code in _globals, _locals
                except:
                    # 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

                    map(self.write,list)

                    self.write(ResponseException + '\n')
                    
                return
            
            if cmd == RequestBanner:
                self.write('%s%s\n' % (ResponseBanner, 
                    str(("Python %s" % sys.version, socket.gethostname(), self.variant))))
                return
            
            if cmd == RequestCapabilities:
                self.write('%s%d, "Python"\n' % (ResponseCapabilities, 
                    self.clientCapabilities))
                return
                
            if cmd == RequestCompletion:
                self.completionList(arg)
                return
            
            if cmd == RequestSetFilter:
                scope, filterString = eval(arg)
                self.generateFilterObjects(int(scope), filterString)
                return
            
            if cmd == RequestUTPrepare:
                fn, tn, cov, covname, erase = arg.split('|')
                sys.path.insert(0,os.path.dirname(os.path.abspath(fn)))

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                try:
                    import unittest
                    utModule = __import__(tn)
                    try:
                        self.test = unittest.defaultTestLoader.loadTestsFromName("suite", utModule)
                    except AttributeError:
                        self.test = unittest.defaultTestLoader.loadTestsFromModule(utModule)
                except:
                    exc_type, exc_value, exc_tb = sys.exc_info()
                    self.write('%s%s\n' % (ResponseUTPrepared,
                        str((0, str(exc_type), str(exc_value)))))
                    self.handleException()
                    return
                    
                # generate a coverage object
                if int(cov):
                    import PyCoverage
                    self.cover = PyCoverage.coverage(covname)
                    if int(erase):
                        self.cover.erase()
                else:
                    self.cover = None
                
                self.write('%s%s\n' % (ResponseUTPrepared,
                    str((self.test.countTestCases(), "", ""))))
                return
                
            if cmd == 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('%s\n' % ResponseUTFinished)
                return
                
            if cmd == RequestUTStop:
                self.testResult.stop()
                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 = 0
            self.rawLine = line
            self.eventExit = 1
            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()
            map(self.write,traceback.format_exception_only(sys.last_type,sys.last_value))
            self.buffer = ''

            self.handleException()
        else:
            if code is None:
                self.pendingResponse = ResponseContinue
            else: 
                self.buffer = ''

                try:
                    if self.running is None:
                        exec code in 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:
                                self.running = None
                                _globals = self.debugMod.__dict__
                                _locals = _globals
                            else:
                                _globals = self.currentThread.getCurrentFrame().f_globals
                                _locals = self.currentThread.getCurrentFrame().f_locals
                        # reset sys.stdout to our redirector (unconditionally)
                        if _globals.has_key("sys"):
                            __stdout = _globals["sys"].stdout
                            _globals["sys"].stdout = self.writestream
                            exec code in _globals, _locals
                            _globals["sys"].stdout = __stdout
                        elif _locals.has_key("sys"):
                            __stdout = _locals["sys"].stdout
                            _locals["sys"].stdout = self.writestream
                            exec code in _globals, _locals
                            _locals["sys"].stdout = __stdout
                        else:
                            exec code in _globals, _locals
                except SystemExit, exc:
                    self.progTerminated(exc.code)
                except:
                    # 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

                    map(self.write,list)

                    self.handleException()

    def write(self,s):
        """
        Private method to write data to the output stream.
        
        @param s data to be written (string)
        """
        self.writestream.write(s)
        self.writestream.flush()

    def interact(self):
        """
        Private method to Interact with  the debugger.
        """
        global DebugClientInstance

        self.setDescriptors(self.readstream,self.writestream)
        DebugClientInstance = self

        if not self.passive:
            # At this point simulate an event loop.
            self.eventLoop()

    def eventLoop(self):
        """
        Private method implementing our event loop.
        """
        self.eventExit = None

        while self.eventExit is None:
            wrdy = []

            if AsyncPendingWrite(self.writestream):
                wrdy.append(self.writestream)

            if AsyncPendingWrite(self.errorstream):
                wrdy.append(self.errorstream)

            rrdy, wrdy, xrdy = select.select([self.readstream],wrdy,[])

            if self.readstream in rrdy:
                self.readReady(self.readstream.fileno())

            if self.writestream in wrdy:
                self.writeReady(self.writestream.fileno())

            if self.errorstream in wrdy:
                self.writeReady(self.errorstream.fileno())

        self.eventExit = None

    def eventPoll(self):
        """
        Private method to poll for events like 'set break point'.
        """
        # the choice of a ~0.5 second poll interval is arbitrary.
        lasteventpolltime = getattr(self, 'lasteventpolltime', time.time())
        now = time.time()
        if now - lasteventpolltime < 0.5:
            self.lasteventpolltime = lasteventpolltime
            return
        else:
            self.lasteventpolltime = now

        wrdy = []
        if AsyncPendingWrite(self.writestream):
            wrdy.append(self.writestream)

        if AsyncPendingWrite(self.errorstream):
            wrdy.append(self.errorstream)
        
        # imdediate return if nothing is ready.
        rrdy, wrdy, xrdy = select.select([self.readstream],wrdy,[],0)

        if self.readstream in rrdy:
            self.readReady(self.readstream.fileno())

        if self.writestream in wrdy:
            self.writeReady(self.writestream.fileno())

        if self.errorstream in wrdy:
            self.writeReady(self.errorstream.fileno())
        
    def connectDebugger(self,port,remoteAddress=None,redirect=1):
        """
        Public method to establish a session with the debugger. 
        
        It opens a network connection to the debugger, connects it to stdin, 
        stdout and stderr and saves these file objects in case the application
        being debugged redirects them itself.
        
        @param port the port number to connect to (int)
        @param remoteAddress the network address of the debug server host (string)
        @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
        """
        sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        if remoteAddress is None:
            sock.connect((DebugAddress,port))
        else:
            sock.connect((remoteAddress,port))
        sock.setblocking(0)

        self.readstream = AsyncFile(sock,sys.stdin.mode,sys.stdin.name)
        self.writestream = AsyncFile(sock,sys.stdout.mode,sys.stdout.name)
        self.errorstream = AsyncFile(sock,sys.stderr.mode,sys.stderr.name)
        
        if redirect:
            sys.stdin = self.readstream
            sys.stdout = self.writestream
            sys.stderr = self.errorstream
        
        # attach to the main thread here
        self.attachThread(mainThread=1)

    def unhandled_exception(self, exctype, excval, exctb):
        """
        Private method called to report an uncaught exception.
        
        @param exctype the type of the exception
        @param excval data about the exception
        @param exctb traceback for the exception
        """
        self.mainThread.user_exception(None, (exctype,excval,exctb), 1)
        
    def absPath(self,fn):
        """
        Private method to convert a filename to an absolute name.

        sys.path is used as a set of possible prefixes. The name stays 
        relative if a file could not be found.
        
        @param fn filename (string)
        @return the converted filename (string)
        """
        if os.path.isabs(fn):
            return fn

        # Check the cache.
        if self.fncache.has_key(fn):
            return self.fncache[fn]

        # Search sys.path.
        for p in sys.path:
            afn = os.path.abspath(os.path.join(p,fn))

            if os.path.exists(afn):
                self.fncache[fn] = afn
                d = os.path.dirname(afn)
                if (d not in sys.path) and (d not in self.dircache):
                    self.dircache.append(d)
                return afn

        # Search the additional directory cache
        for p in self.dircache:
            afn = os.path.abspath(os.path.join(p,fn))
            
            if os.path.exists(afn):
                self.fncache[fn] = afn
                return afn
                
        # Nothing found.
        return fn

    def shouldSkip(self, fn):
        """
        Public method to check if a file should be skipped.
        
        @param fn filename to be checked
        @return non-zero if fn represents a file we are 'skipping', zero otherwise.
        """
        if self.mainThread.tracePython:     # trace into Python library
            return 0
            
        # Eliminate anything that is part of the Python installation.
        afn = self.absPath(fn)
        if afn.find(self.skipdir) == 0:
            return 1
            
        return 0
        
    def getRunning(self):
        """
        Public method to return the main script we are currently running.
        """
        return self.running

    def progTerminated(self,status):
        """
        Private method to tell the debugger that the program has terminated.
        
        @param status the return status
        """
        if status is None:
            status = 0
        else:
            try:
                int(status)
            except:
                status = 1

        if self.running:
            self.set_quit()
            self.running = None
        self.write('%s%d\n' % (ResponseExit,status))
        
        # reset coding
        try:
            sys.setappdefaultencoding(self.defaultCoding)
        except AttributeError:
            pass

    def dumpVariables(self, frmnr, scope, filter):
        """
        Private method to return the variables of a frame to the debug server.
        
        @param frmnr distance of frame reported on. 0 is the current frame (int)
        @param scope 1 to report global variables, 0 for local variables (int)
        @param filter the indices of variable types to be filtered (list of int)
        """
        f = self.currentThread.getCurrentFrame()
        
        while f is not None and frmnr > 0:
            f = f.f_back
            frmnr -= 1
        
        if f is None:
            return
        
        if scope:
            dict = f.f_globals
        else:
            dict = f.f_locals
            
            if f.f_globals is f.f_locals:
                scope = -1
                
        varlist = [scope]
        
        if scope != -1:
            keylist = dict.keys()
            
            vlist = self.formatVariablesList(keylist, dict, scope, filter)
            varlist.extend(vlist)
            
        self.write('%s%s\n' % (ResponseVariables, str(varlist)))
    
    def dumpVariable(self, var, frmnr, scope, filter):
        """
        Private method to return the variables of a frame to the debug server.
        
        @param var list encoded name of the requested variable (list of strings)
        @param frmnr distance of frame reported on. 0 is the current frame (int)
        @param scope 1 to report global variables, 0 for local variables (int)
        @param filter the indices of variable types to be filtered (list of int)
        """
        f = self.currentThread.getCurrentFrame()
        
        while f is not None and frmnr > 0:
            f = f.f_back
            frmnr -= 1
        
        if f is None:
            return
        
        if scope:
            dict = f.f_globals
        else:
            dict = f.f_locals
            
            if f.f_globals is f.f_locals:
                scope = -1
                
        varlist = [scope, var]
        
        if scope != -1:
            # search the correct dictionary
            i = 0
            rvar = var[:]
            dictkeys = None
            obj = None
            isDict = 0
            formatSequences = 0
            access = ""
            oaccess = ""
            odict = dict
            
            while i < len(var):
                if len(dict):
                    udict = dict
                ndict = {}
                # this has to be in line with VariablesViewer.indicators
                if var[i][-2:] in ["[]", "()", "{}"]:
                    if i+1 == len(var):
                        if var[i][:-2] == '...':
                            dictkeys = [var[i-1]]
                        else:
                            dictkeys = [var[i][:-2]]
                        formatSequences = 1
                        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:] == "{}":
                            isDict = 1
                        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])
                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)
                            exec 'obj = dict%s' % access
                            if mdict and not "sipThis" in mdict.keys():
                                del rvar[0:2]
                                access = ""
                        except:
                            pass
                        try:
                            cdict = {}
                            exec 'slv = dict%s.__slots__' % access
                            for v in slv:
                                try:
                                    exec 'cdict[v] = dict%s.%s' % (access, v)
                                except:
                                    pass
                            ndict.update(cdict)
                            exec 'obj = dict%s' % access
                            access = ""
                        except:
                            pass
                    else:
                        try:
                            ndict.update(dict[var[i]].__dict__)
                            del rvar[0]
                            obj = dict[var[i]]
                        except:
                            pass
                        try:
                            cdict = {}
                            slv = dict[var[i]].__slots__
                            for v in slv:
                                try:
                                    exec 'cdict[v] = dict[var[i]].%s' % v
                                except:
                                    pass
                            ndict.update(cdict)
                            obj = dict[var[i]]
                        except:
                            pass
                    odict = dict
                    dict = ndict
                i += 1
            
            if ("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 ["[]", "()", "{}"]:
                    exec 'qvar = udict["%s"][%s]' % (rvar[0][:-2], rvar[1])
                else:
                    qvar = udict[var[-1]]
                vlist = self.formatQtVariable(qvar)
            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:
                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)))
        
        self.write('%s%s\n' % (ResponseVariable, str(varlist)))
        
    def formatQtVariable(self, value):
        """
        Private method to produce a formated output of a simple Qt type.
        
        @param value variable to be formated
        @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.
        """
        try:
            from qt import Qt, QString, QPoint, QRect, QSize, QColor, QCString, \
                QDate, QDateTime, QTime, QDir, QFile, QFont, QStringList, QUrl
        except ImportError:
            return []
            
        # XML stuff
        try:
            from qtxml import QDomAttr, QDomCharacterData, QDomComment, \
                QDomElement, QDomText
            xmlAvail = 1
        except ImportError:
            xmlAvail = 0
            
        # network stuff
        try:
            from qtnetwork import QHostAddress
            netAvail = 1
        except ImportError:
            netAvail = 0
            
        varlist = []
        
        if isinstance(value, QString):
            varlist.append(("", "QString", "%s" % unicode(value)))
        elif isinstance(value, QPoint):
            varlist.append(("x", "int", "%d" % value.x()))
            varlist.append(("y", "int", "%d" % value.y()))
        elif isinstance(value, QRect):
            varlist.append(("x", "int", "%d" % value.x()))
            varlist.append(("y", "int", "%d" % value.y()))
            varlist.append(("width", "int", "%d" % value.width()))
            varlist.append(("height", "int", "%d" % value.height()))
        elif isinstance(value, QSize):
            varlist.append(("width", "int", "%d" % value.width()))
            varlist.append(("height", "int", "%d" % value.height()))
        elif isinstance(value, QColor):
            varlist.append(("name", "QString", "%s" % value.name()))
            r, g, b = value.getRgb()
            varlist.append(("rgb", "int", "%d, %d, %d" % (r, g, b)))
            h, s, v = value.getHsv()
            varlist.append(("hsv", "int", "%d, %d, %d" % (h, s, v)))
        elif isinstance(value, QCString):
            varlist.append(("", "QCString", "%s" % unicode(value)))
        elif isinstance(value, QDate):
            varlist.append(("", "QDate", "%s" % unicode(value.toString(Qt.LocalDate))))
        elif isinstance(value, QDateTime):
            varlist.append(("", "QDateTime", "%s" % unicode(value.toString(Qt.LocalDate))))
        elif isinstance(value, QTime):
            varlist.append(("", "QTime", "%s" % unicode(value.toString(Qt.LocalDate))))
        elif isinstance(value, QDir):
            varlist.append(("path", "QString", "%s" % unicode(value.path())))
            varlist.append(("absPath", "QString", "%s" % unicode(value.absPath())))
        elif isinstance(value, QFile):
            varlist.append(("name", "QString", "%s" % unicode(value.name())))
        elif isinstance(value, QFont):
            varlist.append(("family", "QString", "%s" % unicode(value.family())))
            varlist.append(("pointSize", "int", "%d" % value.pointSize()))
            varlist.append(("weight", "int", "%d" % value.weight()))
            varlist.append(("bold", "bool", "%s" % value.bold()))
            varlist.append(("italic", "bool", "%s" % value.italic()))
        elif isinstance(value, QStringList):
            for s in value:
                varlist.append(("", "QString", "%s" % unicode(s)))
        elif isinstance(value, QUrl):
            varlist.append(("host", "QString", "%s" % unicode(value.host())))
            varlist.append(("protocol", "QString", "%s" % unicode(value.protocol())))
            varlist.append(("user", "QString", "%s" % unicode(value.user())))
            varlist.append(("password", "QString", "%s" % unicode(value.password())))
            varlist.append(("port", "QString", "%s" % unicode(value.port())))
            varlist.append(("path", "QString", "%s" % unicode(value.path())))
            varlist.append(("query", "QString", "%s" % unicode(value.query())))
            
        # XML stuff
        elif xmlAvail and isinstance(value, QDomAttr):
            varlist.append(("name", "QString", "%s" % unicode(value.name())))
            varlist.append(("value", "QString", "%s" % unicode(value.value())))
        elif xmlAvail and isinstance(value, QDomCharacterData):
            varlist.append(("data", "QString", "%s" % unicode(value.data())))
        elif xmlAvail and isinstance(value, QDomComment):
            varlist.append(("data", "QString", "%s" % unicode(value.data())))
        elif xmlAvail and isinstance(value, QDomElement):
            varlist.append(("tagName", "QString", "%s" % unicode(value.tagName())))
            varlist.append(("text", "QString", "%s" % unicode(value.text())))
        elif xmlAvail and isinstance(value, QDomText):
            varlist.append(("data", "QString", "%s" % unicode(value.data())))
            
        # Networking stuff
        elif netAvail and isinstance(value, QHostAddress):
            varlist.append(("address", "QHostAddress", "%s" % unicode(value.toString())))
            
        return varlist
        
    def formatVariablesList(self, keylist, dict, scope, filter = [], formatSequences = 0):
        """
        Private method to produce a formated variables list.
        
        The dictionary passed in to it is scanned. Variables are
        only added to the list, if their type is not contained 
        in the filter list and their name doesn't match any of the filter expressions.
        The formated variables list (a list of tuples of 3 values) is returned.
        
        @param keylist keys of the dictionary
        @param dict the dictionary to be scanned
        @param scope 1 to filter using the globals filter, 0 using the locals filter (int).
            Variables are only added to the list, if their name do not match any of the
            filter expressions.
        @param filter the indices of variable types to be filtered. Variables are
            only added to the list, if their type is not contained in the filter 
            list.
        @param formatSequences flag indicating, that sequence or dictionary variables
            should be formatted. If it is 0 (or false), just the number of items contained
            in these variables is returned. (boolean)
        @return A tuple consisting of a list of formatted variables. Each variable
            entry is a tuple of three elements, the variable name, its type and 
            value.
        """
        varlist = []
        if scope:
            patternFilterObjects = self.globalsFilterObjects
        else:
            patternFilterObjects = self.localsFilterObjects
        
        for key in keylist:
            # filter based on the filter pattern
            matched = 0
            for pat in patternFilterObjects:
                if pat.match(str(key)):
                    matched = 1
                    break
            if matched:
                continue
            
            # filter hidden attributes (filter #0)
            if 0 in filter and str(key)[:2] == '__':
                continue
            
            # special handling for '__builtins__' (it's way too big)
            if key == '__builtins__':
                rvalue = '<module __builtin__ (built-in)>'
                valtype = 'module'
            else:
                value = dict[key]
                valtypestr = str(type(value))[1:-1]
                    
                if valtypestr.split(' ',1)[0] == 'class':
                    # handle new class type of python 2.2+
                    if ConfigVarTypeStrings.index('instance') in filter:
                        continue
                    valtype = valtypestr
                else:
                    valtype = valtypestr[6:-1]
                    try:
                        if ConfigVarTypeStrings.index(valtype) in filter:
                            continue
                    except ValueError:
                        if valtype == "classobj":
                            if ConfigVarTypeStrings.index('instance') in filter:
                                continue
                        elif ConfigVarTypeStrings.index('other') in filter:
                            continue
                    
                try:
                    if valtype not in ['list', 'tuple', 'dict']:
                        rvalue = repr(value)
                        if valtype.startswith('class') and \
                           rvalue[0] in ['{', '(', '[']:
                            rvalue = ""
                    else:
                        if valtype == 'dict':
                            rvalue = "%d" % len(value.keys())
                        else:
                            rvalue = "%d" % len(value)
                except:
                    rvalue = ''
                
            if formatSequences:
                if str(key) == key:
                    key = "'%s'" % key
                else:
                    key = str(key)
            varlist.append((key, valtype, rvalue))
        
        return varlist
        
    def generateFilterObjects(self, scope, filterString):
        """
        Private slot to convert a filter string to a list of filter objects.
        
        @param scope 1 to generate filter for global variables, 0 for local variables (int)
        @param filterString string of filter patterns separated by ';'
        """
        patternFilterObjects = []
        for pattern in filterString.split(';'):
            patternFilterObjects.append(re.compile('^%s$' % pattern))
        if scope:
            self.globalsFilterObjects = patternFilterObjects[:]
        else:
            self.localsFilterObjects = patternFilterObjects[:]
        
    def completionList(self, text):
        """
        Private slot to handle the request for a commandline completion list.
        
        @param text the text to be completed (string)
        """
        completerDelims = ' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?'
        
        completions = []
        state = 0
        # find position of last delim character
        pos = -1
        while pos >= -len(text):
            if text[pos] in completerDelims:
                if pos == -1:
                    text = ''
                else:
                    text = text[pos+1:]
                break
            pos -= 1
        
        try:
            comp = self.complete(text, state)
        except:
            comp = None
        while comp is not None:
            completions.append(comp)
            state += 1
            try:
                comp = self.complete(text, state)
            except:
                comp = None
            
        self.write("%s%s||%s\n" % (ResponseCompletion, str(completions), text))

    def startDebugger(self, filename=None, host=None, port=None,
            enableTrace=1, exceptions=1, redirect=1):
        """
        Method used to start the remote debugger.
        
        @param filename the program to be debugged (string)
        @param host hostname of the debug server (string)
        @param port portnumber of the debug server (int)
        @param enableTrace flag to enable the tracing function (boolean)
        @param exceptions flag to enable exception reporting of the IDE (boolean)
        """
        global debugClient, debugging
        if host is None:
            host = os.getenv('ERICHOST', 'localhost')
        if port is None:
            port = os.getenv('ERICPORT', 42424)
            
        self.connectDebugger(port, socket.gethostbyname(host), redirect)
        if filename is not None:
            self.running = os.path.abspath(filename)
        else:
            try:
                self.running = os.path.abspath(sys.argv[0])
            except:
                self.running = None
        if self.running:
            coding = self.get_coding(self.running)
            try:
                sys.setappdefaultencoding(coding)
            except AttributeError:
                pass
        self.passive = 1
        self.write("%s%s|%d\n" % (PassiveStartup, self.running, exceptions))
        self.interact()
        
        # setup the debugger variables
        self.fncache = {}
        self.dircache = []
        self.mainFrame = None
        self.inRawMode = 0
        
        self.attachThread(mainThread=1)
        
        # set the system exception handling function to ensure, that
        # we report on all unhandled exceptions
        sys.excepthook = self.unhandled_exception
        
        # now start debugging
        if enableTrace:
            self.mainThread.set_trace()
            debugging = 1
        else:
            debugging = 0
        
    def startProgInDebugger(self, progargs, wd = '', host=None, 
            port=None, exceptions=1, tracePython=0, redirect=1):
        """
        Method used to start the remote debugger.
        
        @param progargs commandline for the program to be debugged 
            (list of strings)
        @param wd working directory for the program execution (string)
        @param host hostname of the debug server (string)
        @param port portnumber of the debug server (int)
        @param exceptions flag to enable exception reporting of the IDE (boolean)
        @param tracePython flag to enable tracing into the Python library (boolean)
        @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
        """
        global debugging
        debugging = 1
        
        if host is None:
            host = os.getenv('ERICHOST', 'localhost')
        if port is None:
            port = os.getenv('ERICPORT', 42424)
            
        self.connectDebugger(port, socket.gethostbyname(host), redirect)
        
        self.fncache = {}
        self.dircache = []
        sys.argv = progargs[:]
        sys.argv[0] = os.path.abspath(sys.argv[0])
        sys.path[0] = os.path.dirname(sys.argv[0])
        if wd == '':
            os.chdir(sys.path[0])
        else:
            os.chdir(wd)
        self.running = sys.argv[0]
        coding = self.get_coding(self.running)
        try:
            sys.setappdefaultencoding(coding)
        except AttributeError:
            pass
        self.mainFrame = None
        self.inRawMode = 0
        
        self.passive = 1
        self.write("%s%s|%d\n" % (PassiveStartup, self.running, exceptions))
        self.interact()
        
        self.attachThread(mainThread = 1)
        self.mainThread.tracePython = tracePython
        
        # set the system exception handling function to ensure, that
        # we report on all unhandled exceptions
        sys.excepthook = self.unhandled_exception
        
        # 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
        res = self.mainThread.run('execfile(' + `self.running` + ')',self.debugMod.__dict__)
        self.progTerminated(res)

    def run_call(self, scriptname, func, *args):
        """
        Public method used to start the remote debugger and call a function.
        
        @param scriptname name of the script to be debugged (string)
        @param func function to be called
        @param *args arguments being passed to func
        @return result of the function call
        """
        self.startDebugger(scriptname, enableTrace = 0)
        res = self.mainThread.runcall(func, *args)
        self.progTerminated(res)
        return res
        
    def main(self):
        """
        Public method implementing the main method.
        """
        try:
            sys.setappdefaultencoding(self.defaultCoding)
        except AttributeError:
            pass
        
        if '--' in sys.argv:
            args = sys.argv[1:]
            host = None
            port = None
            wd = ''
            tracePython = 0
            exceptions = 1
            redirect = 1
            while args[0]:
                if args[0] == '-h':
                    host = args[1]
                    del args[0]
                    del args[0]
                elif args[0] == '-p':
                    port = args[1]
                    del args[0]
                    del args[0]
                elif args[0] == '-w':
                    wd = args[1]
                    del args[0]
                    del args[0]
                elif args[0] == '-t':
                    tracePython = 1
                    del args[0]
                elif args[0] == '-e':
                    exceptions = 0
                    del args[0]
                elif args[0] == '-n':
                    redirect = 0
                    del args[0]
                elif args[0] == '--':
                    del args[0]
                    break
                else:   # unknown option
                    del args[0]
            if not args:
                print "No program given. Aborting!"
            else:
                self.startProgInDebugger(args, wd, host, port, 
                                         exceptions=exceptions, tracePython=tracePython,
                                         redirect=redirect)
        else:
            try:
                port = int(sys.argv[1])
            except:
                port = -1
            try:
                redirect = int(sys.argv[2])
            except:
                redirect = 1
            try:
                remoteAddress = sys.argv[3]
            except:
                remoteAddress = None
            sys.argv = ['']
            sys.path.insert(0, '')
            if port >= 0:
                self.connectDebugger(port, remoteAddress, redirect)
                self.interact()
