Debugger/DebugServer.py

Tue, 15 Feb 2011 20:04:42 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 15 Feb 2011 20:04:42 +0100
changeset 893
5907b8d05b46
parent 892
754efacb0fbb
child 945
8cd4d08fa9f6
child 986
12880ccc4d15
permissions
-rw-r--r--

Forgot a statement in the last change.

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

# Copyright (c) 2007 - 2011 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the debug server.
"""

import os

from PyQt4.QtCore import *
from PyQt4.QtNetwork import QTcpServer, QHostAddress, QHostInfo

from E5Gui.E5Application import e5App
from E5Gui import E5MessageBox

from .BreakPointModel import BreakPointModel
from .WatchPointModel import WatchPointModel
from . import DebugClientCapabilities

import Preferences
import Utilities


DebuggerInterfaces = [
    "DebuggerInterfacePython",
    "DebuggerInterfacePython3",
    "DebuggerInterfaceRuby",
    "DebuggerInterfaceNone",
]

class DebugServer(QTcpServer):
    """
    Class implementing the debug server embedded within the IDE.
    
    @signal clientProcessStdout(str) emitted after the client has sent some output
            via stdout
    @signal clientProcessStderr(str) emitted after the client has sent some output
            via stderr
    @signal clientOutput(str) emitted after the client has sent some output
    @signal clientRawInputSent() emitted after the data was sent to the debug client
    @signal clientLine(filename, lineno, forStack) emitted after the debug client 
            has executed a line of code
    @signal clientStack(stack) emitted after the debug client has executed a
            line of code
    @signal clientThreadList(currentId, threadList) emitted after a thread list
            has been received
    @signal clientThreadSet() emitted after the client has acknowledged the change
            of the current thread
    @signal clientVariables(scope, variables) emitted after a variables dump has 
            been received
    @signal clientVariable(scope, variables) emitted after a dump for one class 
            variable has been received
    @signal clientStatement(bool) emitted after an interactive command has
            been executed. The parameter is 0 to indicate that the command is
            complete and 1 if it needs more input.
    @signal clientException(exception) emitted after an exception occured on the 
            client side
    @signal clientSyntaxError(exception) emitted after a syntax error has been detected
            on the client side
    @signal clientExit(int) emitted with the exit status after the client has exited
    @signal clientClearBreak(filename, lineno) emitted after the debug client
            has decided to clear a temporary breakpoint
    @signal clientBreakConditionError(fn, lineno) emitted after the client has signaled
            a syntax error in a breakpoint condition
    @signal clientClearWatch(condition) emitted after the debug client
            has decided to clear a temporary watch expression
    @signal clientWatchConditionError(condition) emitted after the client has signaled
            a syntax error in a watch expression
    @signal clientRawInput(prompt, echo) emitted after a raw input request was received
    @signal clientBanner(banner) emitted after the client banner was received
    @signal clientCapabilities(int capabilities, string cltype) emitted after the clients
            capabilities were received
    @signal clientCompletionList(completionList, text) emitted after the client
            the commandline completion list and the reworked searchstring was
            received from the client
    @signal passiveDebugStarted(str, bool) emitted after the debug client has connected in
            passive debug mode
    @signal clientGone(bool) emitted if the client went away (planned or unplanned)
    @signal utPrepared(nrTests, exc_type, exc_value) emitted after the client has
            loaded a unittest suite
    @signal utFinished() emitted after the client signalled the end of the unittest
    @signal utStartTest(testname, testdocu) emitted after the client has started 
            a test
    @signal utStopTest() emitted after the client has finished a test
    @signal utTestFailed(testname, exc_info) emitted after the client reported 
            a failed test
    @signal utTestErrored(testname, exc_info) emitted after the client reported 
            an errored test
    """
    clientClearBreak = pyqtSignal(str, int)
    clientClearWatch = pyqtSignal(str)
    clientGone = pyqtSignal(bool)
    clientProcessStdout = pyqtSignal(str)
    clientProcessStderr = pyqtSignal(str)
    clientRawInputSent = pyqtSignal()
    clientOutput = pyqtSignal(str)
    clientLine = pyqtSignal(str, int, bool)
    clientStack = pyqtSignal(list)
    clientThreadList = pyqtSignal(int, list)
    clientThreadSet = pyqtSignal()
    clientVariables = pyqtSignal(int, list)
    clientVariable = pyqtSignal(int, list)
    clientStatement = pyqtSignal(bool)
    clientException = pyqtSignal(str, str, list)
    clientSyntaxError = pyqtSignal(str, str, int, int)
    clientExit = pyqtSignal(int)
    clientBreakConditionError = pyqtSignal(str, int)
    clientWatchConditionError = pyqtSignal(str)
    clientRawInput = pyqtSignal(str, bool)
    clientBanner = pyqtSignal(str, str, str)
    clientCapabilities = pyqtSignal(int, str)
    clientCompletionList = pyqtSignal(list, str)
    utPrepared = pyqtSignal(int, str, str)
    utStartTest = pyqtSignal(str, str)
    utStopTest = pyqtSignal()
    utTestFailed = pyqtSignal(str, str)
    utTestErrored = pyqtSignal(str, str)
    utFinished = pyqtSignal()
    passiveDebugStarted = pyqtSignal(str, bool)
    
    def __init__(self):
        """
        Constructor
        """
        QTcpServer.__init__(self)
        
        # create our models
        self.breakpointModel = BreakPointModel(self)
        self.watchpointModel = WatchPointModel(self)
        self.watchSpecialCreated = \
            self.trUtf8("created", "must be same as in EditWatchpointDialog") 
        self.watchSpecialChanged = \
            self.trUtf8("changed", "must be same as in EditWatchpointDialog")
        
        self.networkInterface = Preferences.getDebugger("NetworkInterface")
        if self.networkInterface == "all":
            hostAddress = QHostAddress("0.0.0.0")#QHostAddress.Any)
        elif self.networkInterface == "allv6":
            hostAddress = QHostAddress("::")#QHostAddress.AnyIPv6)
        else:
            hostAddress = QHostAddress(Preferences.getDebugger("NetworkInterface"))
        if Preferences.getDebugger("PassiveDbgEnabled"):
            socket = Preferences.getDebugger("PassiveDbgPort") # default: 42424
            self.listen(hostAddress, socket)
            self.passive = True
            self.passiveClientExited = False
        else:
            self.listen(hostAddress)
            self.passive = False
        
        self.debuggerInterface = None
        self.debugging = False
        self.running = False
        self.clientProcess = None
        self.clientType = \
            Preferences.Prefs.settings.value('DebugClient/Type', 'Python3')
        self.lastClientType = ''
        self.__autoClearShell = False
        
        self.clientClearBreak.connect(self.__clientClearBreakPoint)
        self.clientClearWatch.connect(self.__clientClearWatchPoint)
        self.newConnection[()].connect(self.__newConnection)
        
        self.breakpointModel.rowsAboutToBeRemoved.connect(self.__deleteBreakPoints)
        self.breakpointModel.dataAboutToBeChanged.connect(
            self.__breakPointDataAboutToBeChanged)
        self.breakpointModel.dataChanged.connect(self.__changeBreakPoints)
        self.breakpointModel.rowsInserted.connect(self.__addBreakPoints)
        
        self.watchpointModel.rowsAboutToBeRemoved.connect(self.__deleteWatchPoints)
        self.watchpointModel.dataAboutToBeChanged.connect(
            self.__watchPointDataAboutToBeChanged)
        self.watchpointModel.dataChanged.connect(self.__changeWatchPoints)
        self.watchpointModel.rowsInserted.connect(self.__addWatchPoints)
        
        self.__registerDebuggerInterfaces()
        
    def getHostAddress(self, localhost):
        """
        Public method to get the IP address or hostname the debug server is listening.
        
        @param localhost flag indicating to return the address for localhost (boolean)
        @return IP address or hostname (string)
        """
        if self.networkInterface == "all":
            if localhost:
                return "127.0.0.1"
            else:
                return "{0}@@v4".format(QHostInfo.localHostName())
        elif self.networkInterface == "allv6":
            if localhost:
                return "::1"
            else:
                return "{0}@@v6".format(QHostInfo.localHostName())
        else:
            return self.networkInterface
        
    def preferencesChanged(self):
        """
        Public slot to handle the preferencesChanged signal.
        """
        self.__registerDebuggerInterfaces()
        
    def __registerDebuggerInterfaces(self):
        """
        Private method to register the available debugger interface modules.
        """
        self.__clientCapabilities = {}
        self.__clientAssociations = {}
        
        for interface in DebuggerInterfaces:
            modName = "Debugger.{0}".format(interface)
            mod = __import__(modName)
            components = modName.split('.')
            for comp in components[1:]:
                mod = getattr(mod, comp)
            
            clientLanguage, clientCapabilities, clientExtensions = \
                mod.getRegistryData()
            if clientLanguage:
                self.__clientCapabilities[clientLanguage] = clientCapabilities
                for extension in clientExtensions:
                    if extension not in self.__clientAssociations:
                        self.__clientAssociations[extension] = clientLanguage
        
    def getSupportedLanguages(self, shellOnly = False):
        """
        Public slot to return the supported programming languages.
        
        @param shellOnly flag indicating only languages supporting an
            interactive shell should be returned
        @return list of supported languages (list of strings)
        """
        languages = list(self.__clientCapabilities.keys())
        try:
            del languages[languages.index("None")]
        except ValueError:
            pass    # it is not in the list
        
        if shellOnly:
            languages = \
                [lang for lang in languages \
                 if self.__clientCapabilities[lang] & DebugClientCapabilities.HasShell]
        
        return languages[:]
        
    def getExtensions(self, language):
        """
        Public slot to get the extensions associated with the given language.
        
        @param language language to get extensions for (string)
        @return tuple of extensions associated with the language (tuple of strings)
        """
        extensions = []
        for ext, lang in list(self.__clientAssociations.items()):
            if lang == language:
                extensions.append(ext)
        
        return tuple(extensions)
        
    def __createDebuggerInterface(self, clientType = None):
        """
        Private slot to create the debugger interface object.
        
        @param clientType type of the client interface to be created (string)
        """
        if self.lastClientType != self.clientType or clientType is not None:
            if clientType is None:
                clientType = self.clientType
            if clientType == "Python2":
                from .DebuggerInterfacePython import DebuggerInterfacePython
                self.debuggerInterface = DebuggerInterfacePython(self, self.passive)
            elif clientType == "Python3":
                from .DebuggerInterfacePython3 import DebuggerInterfacePython3
                self.debuggerInterface = DebuggerInterfacePython3(self, self.passive)
            elif clientType == "Ruby":
                from .DebuggerInterfaceRuby import DebuggerInterfaceRuby
                self.debuggerInterface = DebuggerInterfaceRuby(self, self.passive)
            elif clientType == "None":
                from .DebuggerInterfaceNone import DebuggerInterfaceNone
                self.debuggerInterface = DebuggerInterfaceNone(self, self.passive)
            else:
                from .DebuggerInterfaceNone import DebuggerInterfaceNone
                self.debuggerInterface = DebuggerInterfaceNone(self, self.passive)
                self.clientType = "None"
        
    def __setClientType(self, clType):
        """
        Private method to set the client type.
        
        @param clType type of client to be started (string)
        """
        if clType is not None and clType in self.getSupportedLanguages():
            self.clientType = clType
            Preferences.Prefs.settings.setValue('DebugClient/Type', 
                self.clientType)
        
    def startClient(self, unplanned = True, clType = None, forProject = False, 
                    runInConsole = False):
        """
        Public method to start a debug client.
        
        @keyparam unplanned flag indicating that the client has died (boolean)
        @keyparam clType type of client to be started (string)
        @keyparam forProject flag indicating a project related action (boolean)
        @keyparam runInConsole flag indicating to start the debugger in a 
            console window (boolean)
        """
        self.running = False
        
        if not self.passive or not self.passiveClientExited: 
            if self.debuggerInterface and self.debuggerInterface.isConnected():
                self.shutdownServer()
                self.clientGone.emit(unplanned and self.debugging)
        
        if clType:
            self.__setClientType(clType)
        
        # only start the client, if we are not in passive mode
        if not self.passive:
            if self.clientProcess:
                self.clientProcess.readyReadStandardError.disconnect(
                    self.__clientProcessError)
                self.clientProcess.readyReadStandardOutput.disconnect(
                    self.__clientProcessOutput)
                self.clientProcess.close()
                self.clientProcess.kill()
                self.clientProcess.waitForFinished(10000)
                self.clientProcess = None
            
            self.__createDebuggerInterface()
            if forProject:
                project = e5App().getObject("Project")
                if not project.isDebugPropertiesLoaded():
                    self.clientProcess, isNetworked = \
                        self.debuggerInterface.startRemote(self.serverPort(), 
                                                           runInConsole)
                else:
                    self.clientProcess, isNetworked = \
                        self.debuggerInterface.startRemoteForProject(self.serverPort(), 
                                                                     runInConsole)
            else:
                self.clientProcess, isNetworked = \
                    self.debuggerInterface.startRemote(self.serverPort(), runInConsole)
            
            if self.clientProcess:
                self.clientProcess.readyReadStandardError.connect(
                    self.__clientProcessError)
                self.clientProcess.readyReadStandardOutput.connect(
                    self.__clientProcessOutput)
                
                if not isNetworked:
                    # the client is connected through stdin and stdout
                    # Perform actions necessary, if client type has changed
                    if self.lastClientType != self.clientType:
                        self.lastClientType = self.clientType
                        self.remoteBanner()
                    elif self.__autoClearShell:
                        self.__autoClearShell = False
                        self.remoteBanner()
                    
                    self.debuggerInterface.flush()
        else:
            self.__createDebuggerInterface("None")

    def __clientProcessOutput(self):
        """
        Private slot to process client output received via stdout.
        """
        output = str(self.clientProcess.readAllStandardOutput(), 
                     Preferences.getSystem("IOEncoding"), 
                     'replace')
        self.clientProcessStdout.emit(output)
        
    def __clientProcessError(self):
        """
        Private slot to process client output received via stderr.
        """
        error = str(self.clientProcess.readAllStandardError(), 
                    Preferences.getSystem("IOEncoding"), 
                    'replace')
        self.clientProcessStderr.emit(error)
        
    def __clientClearBreakPoint(self, fn, lineno):
        """
        Private slot to handle the clientClearBreak signal.
        
        @param fn filename of breakpoint to clear (string)
        @param lineno line number of breakpoint to clear (integer)
        """
        if self.debugging:
            index = self.breakpointModel.getBreakPointIndex(fn, lineno)
            self.breakpointModel.deleteBreakPointByIndex(index)

    def __deleteBreakPoints(self, parentIndex, start, end):
        """
        Private slot to delete breakpoints.
        
        @param parentIndex index of parent item (QModelIndex)
        @param start start row (integer)
        @param end end row (integer)
        """
        if self.debugging:
            for row in range(start, end+1):
                index = self.breakpointModel.index(row, 0, parentIndex)
                fn, lineno = self.breakpointModel.getBreakPointByIndex(index)[0:2]
                self.remoteBreakpoint(fn, lineno, False)

    def __changeBreakPoints(self, startIndex, endIndex):
        """
        Private slot to set changed breakpoints.
        
        @param indexes indexes of changed breakpoints.
        """
        if self.debugging:
            self.__addBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())

    def __breakPointDataAboutToBeChanged(self, startIndex, endIndex):
        """
        Private slot to handle the dataAboutToBeChanged signal of the breakpoint model.
        
        @param startIndex start index of the rows to be changed (QModelIndex)
        @param endIndex end index of the rows to be changed (QModelIndex)
        """
        if self.debugging:
            self.__deleteBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())
        
    def __addBreakPoints(self, parentIndex, start, end):
        """
        Private slot to add breakpoints.
        
        @param parentIndex index of parent item (QModelIndex)
        @param start start row (integer)
        @param end end row (integer)
        """
        if self.debugging:
            for row in range(start, end+1):
                index = self.breakpointModel.index(row, 0, parentIndex)
                fn, line, cond, temp, enabled, ignorecount = \
                    self.breakpointModel.getBreakPointByIndex(index)[:6]
                self.remoteBreakpoint(fn, line, True, cond, temp)
                if not enabled:
                    self.__remoteBreakpointEnable(fn, line, False)
                if ignorecount:
                    self.__remoteBreakpointIgnore(fn, line, ignorecount)

    def __makeWatchCondition(self, cond, special):
        """
        Private method to construct the condition string.
        
        @param cond condition (string)
        @param special special condition (string)
        @return condition string (string)
        """
        if special == "":
            _cond = cond
        else:
            if special == self.watchSpecialCreated:
                _cond = "{0} ??created??".format(cond)
            elif special == self.watchSpecialChanged:
                _cond = "{0} ??changed??".format(cond)
        return _cond
        
    def __splitWatchCondition(self, cond):
        """
        Private method to split a remote watch expression.
        
        @param cond remote expression (string)
        @return tuple of local expression (string) and special condition (string)
        """
        if cond.endswith(" ??created??"):
            cond, special = cond.split()
            special = self.watchSpecialCreated
        elif cond.endswith(" ??changed??"):
            cond, special = cond.split()
            special = self.watchSpecialChanged
        else:
            cond = cond
            special = ""
        
        return cond, special
        
    def __clientClearWatchPoint(self, condition):
        """
        Private slot to handle the clientClearWatch signal.
        
        @param condition expression of watch expression to clear (string)
        """
        if self.debugging:
            cond, special = self.__splitWatchCondition(condition)
            index = self.watchpointModel.getWatchPointIndex(cond, special)
            self.watchpointModel.deleteWatchPointByIndex(index)
        
    def __deleteWatchPoints(self, parentIndex, start, end):
        """
        Private slot to delete watch expressions.
        
        @param parentIndex index of parent item (QModelIndex)
        @param start start row (integer)
        @param end end row (integer)
        """
        if self.debugging:
            for row in range(start, end+1):
                index = self.watchpointModel.index(row, 0, parentIndex)
                cond, special = self.watchpointModel.getWatchPointByIndex(index)[0:2]
                cond = self.__makeWatchCondition(cond, special)
                self.__remoteWatchpoint(cond, False)
        
    def __watchPointDataAboutToBeChanged(self, startIndex, endIndex):
        """
        Private slot to handle the dataAboutToBeChanged signal of the 
        watch expression model.
        
        @param startIndex start index of the rows to be changed (QModelIndex)
        @param endIndex end index of the rows to be changed (QModelIndex)
        """
        if self.debugging:
            self.__deleteWatchPoints(QModelIndex(), startIndex.row(), endIndex.row())
        
    def __addWatchPoints(self, parentIndex, start, end):
        """
        Private slot to set a watch expression.
        
        @param parentIndex index of parent item (QModelIndex)
        @param start start row (integer)
        @param end end row (integer)
        """
        if self.debugging:
            for row in range(start, end + 1):
                index = self.watchpointModel.index(row, 0, parentIndex)
                cond, special, temp, enabled, ignorecount = \
                    self.watchpointModel.getWatchPointByIndex(index)[:5]
                cond = self.__makeWatchCondition(cond, special)
                self.__remoteWatchpoint(cond, True, temp)
                if not enabled:
                    self.__remoteWatchpointEnable(cond, False)
                if ignorecount:
                    self.__remoteWatchpointIgnore(cond, ignorecount)
        
    def __changeWatchPoints(self, startIndex, endIndex):
        """
        Private slot to set changed watch expressions.
        
        @param startIndex start index of the rows to be changed (QModelIndex)
        @param endIndex end index of the rows to be changed (QModelIndex)
        """
        if self.debugging:
            self.__addWatchPoints(QModelIndex(), startIndex.row(), endIndex.row())
        
    def getClientCapabilities(self, type):
        """
        Public method to retrieve the debug clients capabilities.
        
        @param type debug client type (string)
        @return debug client capabilities (integer)
        """
        try:
            return self.__clientCapabilities[type]
        except KeyError:
            return 0    # no capabilities
        
    def __newConnection(self):
        """
        Private slot to handle a new connection.
        """
        sock = self.nextPendingConnection()
        peerAddress = sock.peerAddress().toString()
        if peerAddress not in Preferences.getDebugger("AllowedHosts"):
            # the peer is not allowed to connect
            res = E5MessageBox.yesNo(None,
                self.trUtf8("Connection from illegal host"),
                self.trUtf8("""<p>A connection was attempted by the"""
                    """ illegal host <b>{0}</b>. Accept this connection?</p>""")\
                    .format(peerAddress),
                icon = E5MessageBox.Warning)
            if not res:
                sock.abort()
                return
            else:
                allowedHosts = Preferences.getDebugger("AllowedHosts")
                allowedHosts.append(peerAddress)
                Preferences.setDebugger("AllowedHosts", allowedHosts)
        
        if self.passive:
            self.__createDebuggerInterface(Preferences.getDebugger("PassiveDbgType"))
        
        accepted = self.debuggerInterface.newConnection(sock)
        if accepted:
            # Perform actions necessary, if client type has changed
            if self.lastClientType != self.clientType:
                self.lastClientType = self.clientType
                self.remoteBanner()
            elif self.__autoClearShell:
                self.__autoClearShell = False
                self.remoteBanner()
            elif self.passive:
                self.remoteBanner()
            
            self.debuggerInterface.flush()

    def shutdownServer(self):
        """
        Public method to cleanly shut down.
        
        It closes our socket and shuts down
        the debug client. (Needed on Win OS)
        """
        if self.debuggerInterface is not None:
            self.debuggerInterface.shutdown()

    def remoteEnvironment(self, env):
        """
        Public method to set the environment for a program to debug, run, ...
        
        @param env environment settings (string)
        """
        envlist = Utilities.parseEnvironmentString(env)
        envdict = {}
        for el in envlist:
            try:
                key, value = el.split('=', 1)
                if value.startswith('"') or value.startswith("'"):
                    value = value[1:-1]
                envdict[key] = value
            except UnpackError:
                pass
        self.debuggerInterface.remoteEnvironment(envdict)
        
    def remoteLoad(self, fn, argv, wd, env, autoClearShell = True,
                   tracePython = False, autoContinue = True, forProject = False, 
                   runInConsole = False, autoFork = False, forkChild = False, 
                   clientType = ""):
        """
        Public method to load a new program to debug.
        
        @param fn the filename to debug (string)
        @param argv the commandline arguments to pass to the program (string)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @keyparam autoClearShell flag indicating, that the interpreter window should
            be cleared (boolean)
        @keyparam tracePython flag indicating if the Python library should be traced
            as well (boolean)
        @keyparam autoContinue flag indicating, that the debugger should not stop
            at the first executable line (boolean)
        @keyparam forProject flag indicating a project related action (boolean)
        @keyparam runInConsole flag indicating to start the debugger in a 
            console window (boolean)
        @keyparam autoFork flag indicating the automatic fork mode (boolean)
        @keyparam forkChild flag indicating to debug the child after forking (boolean)
        @keyparam clientType client type to be used (string)
        """
        self.__autoClearShell = autoClearShell
        self.__autoContinue = autoContinue
        
        # Restart the client
        try:
            if clientType:
                self.__setClientType(clientType)
            else:
                self.__setClientType(self.__clientAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.__setClientType('Python3')    # assume it is a Python3 file
        self.startClient(False, forProject = forProject, runInConsole = runInConsole)
        
        self.remoteEnvironment(env)
        
        self.debuggerInterface.remoteLoad(fn, argv, wd, tracePython, autoContinue, 
                                          autoFork, forkChild)
        self.debugging = True
        self.running = True
        self.__restoreBreakpoints()
        self.__restoreWatchpoints()

    def remoteRun(self, fn, argv, wd, env, autoClearShell = True,
                  forProject = False, runInConsole = False, 
                  autoFork = False, forkChild = False, 
                  clientType = ""):
        """
        Public method to load a new program to run.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @keyparam autoClearShell flag indicating, that the interpreter window should
            be cleared (boolean)
        @keyparam forProject flag indicating a project related action (boolean)
        @keyparam runInConsole flag indicating to start the debugger in a 
            console window (boolean)
        @keyparam autoFork flag indicating the automatic fork mode (boolean)
        @keyparam forkChild flag indicating to debug the child after forking (boolean)
        @keyparam clientType client type to be used (string)
        """
        self.__autoClearShell = autoClearShell
        
        # Restart the client
        try:
            if clientType:
                self.__setClientType(clientType)
            else:
                self.__setClientType(self.__clientAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.__setClientType('Python3')    # assume it is a Python3 file
        self.startClient(False, forProject = forProject, runInConsole = runInConsole)
        
        self.remoteEnvironment(env)
        
        self.debuggerInterface.remoteRun(fn, argv, wd, autoFork, forkChild)
        self.debugging = False
        self.running = True

    def remoteCoverage(self, fn, argv, wd, env, autoClearShell = True,
                       erase = False, forProject = False, runInConsole = False, 
                       clientType = ""):
        """
        Public method to load a new program to collect coverage data.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @keyparam autoClearShell flag indicating, that the interpreter window should
            be cleared (boolean)
        @keyparam erase flag indicating that coverage info should be 
            cleared first (boolean)
        @keyparam forProject flag indicating a project related action (boolean)
        @keyparam runInConsole flag indicating to start the debugger in a 
            console window (boolean)
        @keyparam clientType client type to be used (string)
        """
        self.__autoClearShell = autoClearShell
        
        # Restart the client
        try:
            if clientType:
                self.__setClientType(clientType)
            else:
                self.__setClientType(self.__clientAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.__setClientType('Python3')    # assume it is a Python3 file
        self.startClient(False, forProject = forProject, runInConsole = runInConsole)
        
        self.remoteEnvironment(env)
        
        self.debuggerInterface.remoteCoverage(fn, argv, wd, erase)
        self.debugging = False
        self.running = True

    def remoteProfile(self, fn, argv, wd, env, autoClearShell = True,
                      erase = False, forProject = False, 
                      runInConsole = False, 
                      clientType = ""):
        """
        Public method to load a new program to collect profiling data.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @keyparam autoClearShell flag indicating, that the interpreter window should
            be cleared (boolean)
        @keyparam erase flag indicating that timing info should be cleared first (boolean)
        @keyparam forProject flag indicating a project related action (boolean)
        @keyparam runInConsole flag indicating to start the debugger in a 
            console window (boolean)
        @keyparam clientType client type to be used (string)
        """
        self.__autoClearShell = autoClearShell
        
        # Restart the client
        try:
            if clientType:
                self.__setClientType(clientType)
            else:
                self.__setClientType(self.__clientAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.__setClientType('Python3')    # assume it is a Python3 file
        self.startClient(False, forProject = forProject, runInConsole = runInConsole)
        
        self.remoteEnvironment(env)
        
        self.debuggerInterface.remoteProfile(fn, argv, wd, erase)
        self.debugging = False
        self.running = True

    def remoteStatement(self, stmt):
        """
        Public method to execute a Python statement.  
        
        @param stmt the Python statement to execute (string). It
              should not have a trailing newline.
        """
        self.debuggerInterface.remoteStatement(stmt)

    def remoteStep(self):
        """
        Public method to single step the debugged program.
        """
        self.debuggerInterface.remoteStep()

    def remoteStepOver(self):
        """
        Public method to step over the debugged program.
        """
        self.debuggerInterface.remoteStepOver()

    def remoteStepOut(self):
        """
        Public method to step out the debugged program.
        """
        self.debuggerInterface.remoteStepOut()

    def remoteStepQuit(self):
        """
        Public method to stop the debugged program.
        """
        self.debuggerInterface.remoteStepQuit()

    def remoteContinue(self, special = False):
        """
        Public method to continue the debugged program.
        
        @param special flag indicating a special continue operation
        """
        self.debuggerInterface.remoteContinue(special)

    def remoteBreakpoint(self, fn, line, set, cond=None, temp=False):
        """
        Public method to set or clear a breakpoint.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param set flag indicating setting or resetting a breakpoint (boolean)
        @param cond condition of the breakpoint (string)
        @param temp flag indicating a temporary breakpoint (boolean)
        """
        self.debuggerInterface.remoteBreakpoint(fn, line, set, cond, temp)
        
    def __remoteBreakpointEnable(self, fn, line, enable):
        """
        Private method to enable or disable a breakpoint.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param enable flag indicating enabling or disabling a breakpoint (boolean)
        """
        self.debuggerInterface.remoteBreakpointEnable(fn, line, enable)
        
    def __remoteBreakpointIgnore(self, fn, line, count):
        """
        Private method to ignore a breakpoint the next couple of occurrences.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param count number of occurrences to ignore (int)
        """
        self.debuggerInterface.remoteBreakpointIgnore(fn, line, count)
        
    def __remoteWatchpoint(self, cond, set, temp = False):
        """
        Private method to set or clear a watch expression.
        
        @param cond expression of the watch expression (string)
        @param set flag indicating setting or resetting a watch expression (boolean)
        @param temp flag indicating a temporary watch expression (boolean)
        """
        # cond is combination of cond and special (s. watch expression viewer)
        self.debuggerInterface.remoteWatchpoint(cond, set, temp)
    
    def __remoteWatchpointEnable(self, cond, enable):
        """
        Private method to enable or disable a watch expression.
        
        @param cond expression of the watch expression (string)
        @param enable flag indicating enabling or disabling a watch expression (boolean)
        """
        # cond is combination of cond and special (s. watch expression viewer)
        self.debuggerInterface.remoteWatchpointEnable(cond, enable)
    
    def __remoteWatchpointIgnore(self, cond, count):
        """
        Private method to ignore a watch expression the next couple of occurrences.
        
        @param cond expression of the watch expression (string)
        @param count number of occurrences to ignore (int)
        """
        # cond is combination of cond and special (s. watch expression viewer)
        self.debuggerInterface.remoteWatchpointIgnore(cond, count)
    
    def remoteRawInput(self,s):
        """
        Public method to send the raw input to the debugged program.
        
        @param s the raw input (string)
        """
        self.debuggerInterface.remoteRawInput(s)
        self.clientRawInputSent.emit()
        
    def remoteThreadList(self):
        """
        Public method to request the list of threads from the client.
        """
        self.debuggerInterface.remoteThreadList()
        
    def remoteSetThread(self, tid):
        """
        Public method to request to set the given thread as current thread.
        
        @param tid id of the thread (integer)
        """
        self.debuggerInterface.remoteSetThread(tid)
        
    def remoteClientVariables(self, scope, filter, framenr = 0):
        """
        Public method to request the variables of the debugged program.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter list of variable types to filter out (list of int)
        @param framenr framenumber of the variables to retrieve (int)
        """
        self.debuggerInterface.remoteClientVariables(scope, filter, framenr)
        
    def remoteClientVariable(self, scope, filter, var, framenr = 0):
        """
        Public method to request the variables of the debugged program.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter list of variable types to filter out (list of int)
        @param var list encoded name of variable to retrieve (string)
        @param framenr framenumber of the variables to retrieve (int)
        """
        self.debuggerInterface.remoteClientVariable(scope, filter, var, framenr)
        
    def remoteClientSetFilter(self, scope, filter):
        """
        Public method to set a variables filter list.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter regexp string for variable names to filter out (string)
        """
        self.debuggerInterface.remoteClientSetFilter(scope, filter)
        
    def remoteEval(self, arg):
        """
        Public method to evaluate arg in the current context of the debugged program.
        
        @param arg the arguments to evaluate (string)
        """
        self.debuggerInterface.remoteEval(arg)
        
    def remoteExec(self, stmt):
        """
        Public method to execute stmt in the current context of the debugged program.
        
        @param stmt statement to execute (string)
        """
        self.debuggerInterface.remoteExec(stmt)
        
    def remoteBanner(self):
        """
        Public slot to get the banner info of the remote client.
        """
        self.debuggerInterface.remoteBanner()
        
    def remoteCapabilities(self):
        """
        Public slot to get the debug clients capabilities.
        """
        self.debuggerInterface.remoteCapabilities()
        
    def remoteCompletion(self, text):
        """
        Public slot to get the a list of possible commandline completions
        from the remote client.
        
        @param text the text to be completed (string)
        """
        self.debuggerInterface.remoteCompletion(text)

    def remoteUTPrepare(self, fn, tn, tfn, cov, covname, coverase):
        """
        Public method to prepare a new unittest run.
        
        @param fn the filename to load (string)
        @param tn the testname to load (string)
        @param tfn the test function name to load tests from (string)
        @param cov flag indicating collection of coverage data is requested
        @param covname filename to be used to assemble the coverage caches
                filename (string)
        @param coverase flag indicating erasure of coverage data is requested (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.__setClientType(self.__clientAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.__setClientType('Python3')    # assume it is a Python3 file
        self.startClient(False)
        
        self.debuggerInterface.remoteUTPrepare(fn, tn, tfn, cov, covname, coverase)
        self.debugging = False
        self.running = True
        
    def remoteUTRun(self):
        """
        Public method to start a unittest run.
        """
        self.debuggerInterface.remoteUTRun()
        
    def remoteUTStop(self):
        """
        public method to stop a unittest run.
        """
        self.debuggerInterface.remoteUTStop()
        
    def signalClientOutput(self, line):
        """
        Public method to process a line of client output.
        
        @param line client output (string)
        """
        self.clientOutput.emit(line)
        
    def signalClientLine(self, filename, lineno, forStack = False):
        """
        Public method to process client position feedback.
        
        @param filename name of the file currently being executed (string)
        @param lineno line of code currently being executed (integer)
        @param forStack flag indicating this is for a stack dump (boolean)
        """
        self.clientLine.emit(filename, lineno, forStack)
        
    def signalClientStack(self, stack):
        """
        Public method to process a client's stack information.
        
        @param stack list of stack entries. Each entry is a tuple of three
            values giving the filename, linenumber and method
            (list of lists of (string, integer, string))
        """
        self.clientStack.emit(stack)
        
    def signalClientThreadList(self, currentId, threadList):
        """
        Public method to process the client thread list info.
        
        @param currentID id of the current thread (integer)
        @param threadList list of dictionaries containing the thread data
        """
        self.clientThreadList.emit(currentId, threadList)
        
    def signalClientThreadSet(self):
        """
        Public method to handle the change of the client thread.
        """
        self.clientThreadSet.emit()
        
    def signalClientVariables(self, scope, variables):
        """
        Public method to process the client variables info.
        
        @param scope scope of the variables (-1 = empty global, 1 = global, 0 = local)
        @param variables the list of variables from the client
        """
        self.clientVariables.emit(scope, variables)
        
    def signalClientVariable(self, scope, variables):
        """
        Public method to process the client variable info.
        
        @param scope scope of the variables (-1 = empty global, 1 = global, 0 = local)
        @param variables the list of members of a classvariable from the client
        """
        self.clientVariable.emit(scope, variables)
        
    def signalClientStatement(self, more):
        """
        Public method to process the input response from the client.
        
        @param more flag indicating that more user input is required
        """
        self.clientStatement.emit(more)
        
    def signalClientException(self, exceptionType, exceptionMessage, stackTrace):
        """
        Public method to process the exception info from the client.
        
        @param exceptionType type of exception raised (string)
        @param exceptionMessage message given by the exception (string)
        @param stackTrace list of stack entries with the exception position
            first. Each stack entry is a list giving the filename and the linenumber.
        """
        if self.running:
            self.clientException.emit(exceptionType, exceptionMessage, stackTrace)
        
    def signalClientSyntaxError(self, message, filename, lineNo, characterNo):
        """
        Public method to process the syntax error info from the client.
        
        @param message message of the syntax error (string)
        @param filename translated filename of the syntax error position (string)
        @param lineNo line number of the syntax error position (integer)
        @param characterNo character number of the syntax error position (integer)
        """
        if self.running:
            self.clientSyntaxError.emit(message, filename, lineNo, characterNo)
        
    def signalClientExit(self, status):
        """
        Public method to process the client exit status.
        
        @param status exit code as a string (string)
        """
        if self.passive:
            self.__passiveShutDown()
        self.clientExit.emit(int(status))
        if Preferences.getDebugger("AutomaticReset"):
            self.startClient(False)
        if self.passive:
            self.__createDebuggerInterface("None")
            self.signalClientOutput(self.trUtf8('\nNot connected\n'))
            self.signalClientStatement(False)
        self.running = False
        
    def signalClientClearBreak(self, filename, lineno):
        """
        Public method to process the client clear breakpoint command.
        
        @param filename filename of the breakpoint (string)
        @param lineno line umber of the breakpoint (integer)
        """
        self.clientClearBreak.emit(filename, lineno)
        
    def signalClientBreakConditionError(self, filename, lineno):
        """
        Public method to process the client breakpoint condition error info.
        
        @param filename filename of the breakpoint (string)
        @param lineno line umber of the breakpoint (integer)
        """
        self.clientBreakConditionError.emit(filename, lineno)
        
    def signalClientClearWatch(self, condition):
        """
        Public slot to handle the clientClearWatch signal.
        
        @param condition expression of watch expression to clear (string)
        """
        self.clientClearWatch.emit(condition)
        
    def signalClientWatchConditionError(self, condition):
        """
        Public method to process the client watch expression error info.
        
        @param condition expression of watch expression to clear (string)
        """
        self.clientWatchConditionError.emit(condition)
        
    def signalClientRawInput(self, prompt, echo):
        """
        Public method to process the client raw input command.
        
        @param prompt the input prompt (string)
        @param echo flag indicating an echoing of the input (boolean)
        """
        self.clientRawInput.emit(prompt, echo)
        
    def signalClientBanner(self, version, platform, debugClient):
        """
        Public method to process the client banner info.
        
        @param version interpreter version info (string)
        @param platform hostname of the client (string)
        @param debugClient additional debugger type info (string)
        """
        self.clientBanner.emit(version, platform, debugClient)
        
    def signalClientCapabilities(self, capabilities, clientType):
        """
        Public method to process the client capabilities info.
        
        @param capabilities bitmaks with the client capabilities (integer)
        @param clientType type of the debug client (string)
        """
        self.__clientCapabilities[clientType] = capabilities
        self.clientCapabilities.emit(capabilities, clientType)
        
    def signalClientCompletionList(self, completionList, text):
        """
        Public method to process the client auto completion info.
        
        @param completionList list of possible completions (list of strings)
        @param text the text to be completed (string)
        """
        self.clientCompletionList.emit(completionList, text)
        
    def clientUtPrepared(self, result, exceptionType, exceptionValue):
        """
        Public method to process the client unittest prepared info.
        
        @param result number of test cases (0 = error) (integer)
        @param exceptionType exception type (string)
        @param exceptionValue exception message (string)
        """
        self.utPrepared.emit(result, exceptionType, exceptionValue)
        
    def clientUtStartTest(self, testname, doc):
        """
        Public method to process the client start test info.
        
        @param testname name of the test (string)
        @param doc short description of the test (string)
        """
        self.utStartTest.emit(testname, doc)
        
    def clientUtStopTest(self):
        """
        Public method to process the client stop test info.
        """
        self.utStopTest.emit()
        
    def clientUtTestFailed(self, testname, traceback):
        """
        Public method to process the client test failed info.
        
        @param testname name of the test (string)
        @param traceback lines of traceback info (string)
        """
        self.utTestFailed.emit(testname, traceback)
        
    def clientUtTestErrored(self, testname, traceback):
        """
        Public method to process the client test errored info.
        
        @param testname name of the test (string)
        @param traceback lines of traceback info (string)
        """
        self.utTestErrored.emit(testname, traceback)
        
    def clientUtFinished(self):
        """
        Public method to process the client unit test finished info.
        """
        self.utFinished.emit()
        
    def passiveStartUp(self, fn, exc):
        """
        Public method to handle a passive debug connection.
        
        @param fn filename of the debugged script (string)
        @param exc flag to enable exception reporting of the IDE (boolean)
        """
        print(self.trUtf8("Passive debug connection received"))
        self.passiveClientExited = False
        self.debugging = True
        self.running = True
        self.__restoreBreakpoints()
        self.__restoreWatchpoints()
        self.passiveDebugStarted.emit(fn, exc)
        
    def __passiveShutDown(self):
        """
        Private method to shut down a passive debug connection.
        """
        self.passiveClientExited = True
        self.shutdownServer()
        print(self.trUtf8("Passive debug connection closed"))
        
    def __restoreBreakpoints(self):
        """
        Private method to restore the breakpoints after a restart.
        """
        if self.debugging:
            self.__addBreakPoints(QModelIndex(), 0, self.breakpointModel.rowCount()-1)
    
    def __restoreWatchpoints(self):
        """
        Private method to restore the watch expressions after a restart.
        """
        if self.debugging:
            self.__addWatchPoints(QModelIndex(), 0, self.watchpointModel.rowCount()-1)
    
    def getBreakPointModel(self):
        """
        Public slot to get a reference to the breakpoint model object.
        
        @return reference to the breakpoint model object (BreakPointModel)
        """
        return self.breakpointModel

    def getWatchPointModel(self):
        """
        Public slot to get a reference to the watch expression model object.
        
        @return reference to the watch expression model object (WatchPointModel)
        """
        return self.watchpointModel
    
    def isConnected(self):
        """
        Public method to test, if the debug server is connected to a backend.
        
        @return flag indicating a connection (boolean)
        """
        return self.debuggerInterface and self.debuggerInterface.isConnected()

eric ide

mercurial