eric6/Debugger/DebugUI.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 8008
ae9ab1e150dc
child 8142
43248bafe9b2
diff -r 866adc8c315b -r 0acf98cd089a eric6/Debugger/DebugUI.py
--- a/eric6/Debugger/DebugUI.py	Sun Jan 17 13:53:08 2021 +0100
+++ b/eric6/Debugger/DebugUI.py	Mon Feb 01 10:38:16 2021 +0100
@@ -9,23 +9,25 @@
 
 import os
 
-from PyQt5.QtCore import pyqtSignal, QObject, Qt
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt
 from PyQt5.QtGui import QKeySequence
 from PyQt5.QtWidgets import QMenu, QToolBar, QApplication, QDialog
 
+from E5Gui.E5Action import E5Action, createActionGroup
+from E5Gui import E5MessageBox
+
 from UI.Info import Program
+from UI.NotificationWidget import NotificationTypes
 
 from .DebugClientCapabilities import (
     HasDebugger, HasInterpreter, HasProfiler, HasCoverage
 )
+
 import Preferences
 import Utilities
 import UI.PixmapCache
 import UI.Config
 
-from E5Gui.E5Action import E5Action, createActionGroup
-from E5Gui import E5MessageBox
-
 from eric6config import getConfig
 
 
@@ -33,7 +35,8 @@
     """
     Class implementing the debugger part of the UI.
     
-    @signal clientStack(stack) emitted at breaking after a reported exception
+    @signal clientStack(stack, debuggerId) emitted at breaking after a reported
+        exception
     @signal compileForms() emitted if changed project forms should be compiled
     @signal compileResources() emitted if changed project resources should be
         compiled
@@ -41,14 +44,14 @@
         performed
     @signal debuggingStarted(filename) emitted when a debugging session was
         started
-    @signal resetUI() emitted to reset the UI
+    @signal resetUI(full) emitted to reset the UI partially or fully
     @signal exceptionInterrupt() emitted after the execution was interrupted
         by an exception and acknowledged by the user
     @signal appendStdout(msg) emitted when the client program has terminated
         and the display of the termination dialog is suppressed
     """
-    clientStack = pyqtSignal(list)
-    resetUI = pyqtSignal()
+    clientStack = pyqtSignal(list, str)
+    resetUI = pyqtSignal(bool)
     exceptionInterrupt = pyqtSignal()
     compileForms = pyqtSignal()
     compileResources = pyqtSignal()
@@ -101,11 +104,12 @@
             Preferences.Prefs.settings.value('DebugInfo/TracePython', False))
         self.autoContinue = Preferences.toBool(
             Preferences.Prefs.settings.value('DebugInfo/AutoContinue', True))
-        self.forkAutomatically = Preferences.toBool(
+        self.enableMultiprocess = Preferences.toBool(
             Preferences.Prefs.settings.value(
-                'DebugInfo/ForkAutomatically', False))
-        self.forkIntoChild = Preferences.toBool(
-            Preferences.Prefs.settings.value('DebugInfo/ForkIntoChild', False))
+                'DebugInfo/EnableMultiprocess', False))
+        self.multiprocessNoDebugHistory = Preferences.toList(
+            Preferences.Prefs.settings.value(
+                'DebugInfo/MultiprocessNoDebugHistory'))
         
         self.lastDebuggedFile = None
         self.lastStartAction = 0    # 0=None, 1=Script, 2=Project
@@ -113,17 +117,22 @@
         self.lastAction = -1
         self.debugActions = [
             self.__continue, self.__step, self.__stepOver, self.__stepOut,
-            self.__stepQuit, self.__runToCursor, self.__moveInstructionPointer
+            self.__stepQuit, self.__runToCursor, self.__runUntil,
+            self.__moveInstructionPointer
         ]
-        self.localsVarFilter, self.globalsVarFilter = (
+        self.__localsVarFilterList, self.__globalsVarFilterList = (
             Preferences.getVarFilters())
         self.debugViewer.setVariablesFilter(
-            self.globalsVarFilter, self.localsVarFilter)
+            self.__globalsVarFilterList, self.__localsVarFilterList)
+        
+        self.__clientDebuggerIds = set()
         
         # Connect the signals emitted by the debug-server
         debugServer.clientGone.connect(self.__clientGone)
         debugServer.clientLine.connect(self.__clientLine)
+        debugServer.clientDisconnected.connect(self.__clientDisconnected)
         debugServer.clientExit.connect(self.__clientExit)
+        debugServer.lastClientExited.connect(self.__lastClientExited)
         debugServer.clientSyntaxError.connect(self.__clientSyntaxError)
         debugServer.clientException.connect(self.__clientException)
         debugServer.clientSignal.connect(self.__clientSignal)
@@ -135,8 +144,7 @@
             self.__clientWatchConditionError)
         debugServer.passiveDebugStarted.connect(self.__passiveDebugStarted)
         debugServer.clientThreadSet.connect(self.__clientThreadSet)
-        
-        debugServer.clientThreadList.connect(debugViewer.showThreadList)
+        debugServer.clientDebuggerId.connect(self.__clientDebuggerId)
         
         # Connect the signals emitted by the viewmanager
         vm.editorOpened.connect(self.__editorOpened)
@@ -152,18 +160,36 @@
         
         # Set a flag for the passive debug mode
         self.passive = Preferences.getDebugger("PassiveDbgEnabled")
+    
+    def showNotification(self, notification,
+                         kind=NotificationTypes.Information, timeout=None):
+        """
+        Public method to show some notification message.
         
+        @param notification message to be shown
+        @type str
+        @param kind kind of notification to be shown
+        @type NotificationTypes
+        @param timeout timeout for the notification (None = use configured
+            default, 0 = indefinitely)
+        @type int
+        """
+        self.ui.showNotification(
+            UI.PixmapCache.getPixmap("debug48"),
+            self.tr("Notification"), notification, kind=kind, timeout=timeout)
+    
     def variablesFilter(self, scope):
         """
         Public method to get the variables filter for a scope.
         
         @param scope flag indicating global (True) or local (False) scope
-        @return filters list (list of integers)
+        @return filters list
+        @rtype list of str
         """
         if scope:
-            return self.globalsVarFilter[:]
+            return self.__globalsVarFilterList[:]
         else:
-            return self.localsVarFilter[:]
+            return self.__localsVarFilterList[:]
         
     def initActions(self):
         """
@@ -361,6 +387,23 @@
         self.actions.append(act)
         
         act = E5Action(
+            self.tr('Continue Until'),
+            UI.PixmapCache.getIcon("continueUntil"),
+            self.tr('Continue &Until'), Qt.CTRL + Qt.Key_F6, 0,
+            self.debugActGrp, 'dbg_continue_until')
+        act.setStatusTip(self.tr(
+            """Continue running the program from the current line to the"""
+            """ current cursor position or until leaving the current frame"""))
+        act.setWhatsThis(self.tr(
+            """<b>Continue Until</b>"""
+            """<p>Continue running the program from the current line to the"""
+            """ cursor position greater than the current line or until"""
+            """ leaving the current frame.</p>"""
+        ))
+        act.triggered.connect(self.__runUntil)
+        self.actions.append(act)
+        
+        act = E5Action(
             self.tr('Move Instruction Pointer to Cursor'),
             UI.PixmapCache.getIcon("moveInstructionPointer"),
             self.tr('&Jump To Cursor'), Qt.Key_F12, 0,
@@ -912,6 +955,7 @@
         self.argvHistory = []
         self.wdHistory = []
         self.envHistory = []
+        self.multiprocessNoDebugHistory = []
         
         Preferences.Prefs.settings.setValue(
             'DebugInfo/ArgumentsHistory', self.argvHistory)
@@ -919,6 +963,9 @@
             'DebugInfo/WorkingDirectoryHistory', self.wdHistory)
         Preferences.Prefs.settings.setValue(
             'DebugInfo/EnvironmentHistory', self.envHistory)
+        Preferences.Prefs.settings.setValue(
+            'DebugInfo/MultiprocessNoDebugHistory',
+            self.multiprocessNoDebugHistory)
         
     def shutdown(self):
         """
@@ -950,9 +997,10 @@
         Preferences.Prefs.settings.setValue(
             'DebugInfo/AutoContinue', self.autoContinue)
         Preferences.Prefs.settings.setValue(
-            'DebugInfo/ForkAutomatically', self.forkAutomatically)
+            'DebugInfo/EnableMultiprocess', self.enableMultiprocess)
         Preferences.Prefs.settings.setValue(
-            'DebugInfo/ForkIntoChild', self.forkIntoChild)
+            'DebugInfo/MultiprocessNoDebugHistory',
+            self.multiprocessNoDebugHistory)
         
     def shutdownServer(self):
         """
@@ -965,12 +1013,17 @@
         self.debugServer.shutdownServer()
         return True
         
-    def __resetUI(self):
+    def __resetUI(self, fullReset=True):
         """
         Private slot to reset the user interface.
+        
+        @param fullReset flag indicating a full reset is required
+        @type bool
         """
         self.lastAction = -1
         self.debugActGrp.setEnabled(False)
+        self.__clientDebuggerIds.clear()
+        
         if not self.passive:
             if self.editorOpen:
                 editor = self.viewmanager.activeWindow()
@@ -990,15 +1043,32 @@
             else:
                 self.restartAct.setEnabled(False)
             self.stopAct.setEnabled(False)
-        self.resetUI.emit()
+        
+        self.resetUI.emit(fullReset)
+    
+    def __clientDebuggerId(self, debuggerId):
+        """
+        Private slot to track the list of connected debuggers.
         
-    def __clientLine(self, fn, line, forStack):
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        self.__clientDebuggerIds.add(debuggerId)
+    
+    def __clientLine(self, fn, line, debuggerId, threadName, forStack):
         """
         Private method to handle a change to the current line.
         
-        @param fn filename (string)
-        @param line linenumber (int)
-        @param forStack flag indicating this is for a stack dump (boolean)
+        @param fn filename
+        @type str
+        @param line linenumber
+        @type int
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param threadName name of the thread signaling the event
+        @type str
+        @param forStack flag indicating this is for a stack dump
+        @type bool
         """
         self.ui.raise_()
         self.ui.activateWindow()
@@ -1006,80 +1076,80 @@
             self.ui.setDebugProfile()
         self.viewmanager.setFileLine(fn, line)
         if not forStack:
-            self.__getThreadList()
-            self.__getClientVariables()
+            self.__getThreadList(debuggerId)
+            self.__getClientVariables(debuggerId)
 
         self.debugActGrp.setEnabled(True)
+    
+    @pyqtSlot(str)
+    def __clientDisconnected(self, debuggerId):
+        """
+        Private slot to handle a debug client disconnecting its control
+        socket.
         
-    def __clientExit(self, status, message, quiet):
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        Private method to handle the debugged program terminating.
+        self.__clientDebuggerIds.discard(debuggerId)
         
+        if len(self.__clientDebuggerIds) == 0:
+            self.viewmanager.exit()
+            self.__resetUI(fullReset=False)
+    
+    @pyqtSlot(str, int, str, bool, str)
+    def __clientExit(self, program, status, message, quiet, debuggerId):
+        """
+        Private slot to handle the debugged program terminating.
+        
+        @param program name of the exited program
+        @type str
         @param status exit code of the debugged program
         @type int
         @param message exit message of the debugged program
         @type str
         @param quiet flag indicating to suppress exit info display
         @type bool
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.viewmanager.exit()
-
-        self.__resetUI()
+        self.__clientDisconnected(debuggerId)
         
         if not quiet:
-            if (
-                not Preferences.getDebugger("SuppressClientExit") or
-                status != 0
-            ):
-                if message:
-                    info = self.tr("<p>Message: {0}</p>").format(
-                        Utilities.html_uencode(message))
-                else:
-                    info = ""
-                if self.ui.currentProg is None:
-                    E5MessageBox.information(
-                        self.ui, Program,
-                        self.tr('<p>The program has terminated with an exit'
-                                ' status of {0}.</p>{1}').format(status, info))
-                else:
-                    E5MessageBox.information(
-                        self.ui, Program,
-                        self.tr('<p><b>{0}</b> has terminated with an exit'
-                                ' status of {1}.</p>{2}')
-                            .format(os.path.abspath(self.ui.currentProg),
-                                    status, info))
+            if not program:
+                program = self.ui.currentProg
+            
+            if message:
+                info = self.tr("Message: {0}").format(
+                    Utilities.html_uencode(message))
+            else:
+                info = ""
+            if program is None:
+                msg = self.tr(
+                    '<p>The program has terminated with an exit status of'
+                    ' {0}.</p><p>{1}</p>').format(status, info)
             else:
-                if message:
-                    info = self.tr("Message: {0}").format(
-                        Utilities.html_uencode(message))
-                else:
-                    info = ""
-                if self.ui.notificationsEnabled():
-                    if self.ui.currentProg is None:
-                        msg = self.tr(
-                            'The program has terminated with an exit status of'
-                            ' {0}.\n{1}').format(status, info)
-                    else:
-                        msg = self.tr(
-                            '"{0}" has terminated with an exit status of'
-                            ' {1}.\n{2}').format(
-                            os.path.basename(self.ui.currentProg), status,
-                            info)
-                    self.ui.showNotification(
-                        UI.PixmapCache.getPixmap("debug48"),
-                        self.tr("Program terminated"), msg)
-                else:
-                    if self.ui.currentProg is None:
-                        self.appendStdout.emit(self.tr(
-                            'The program has terminated with an exit status'
-                            ' of {0}.\n{1}\n').format(status, info))
-                    else:
-                        self.appendStdout.emit(self.tr(
-                            '"{0}" has terminated with an exit status of'
-                            ' {1}.\n{2}\n').format(
-                            os.path.abspath(self.ui.currentProg), status,
-                            info))
-
+                msg = self.tr(
+                    '<p><b>{0}</b> has terminated with an exit status of'
+                    ' {1}.</p><p>{2}</p>').format(
+                    os.path.basename(program), status, info)
+            if status != 0:
+                timeout = 0
+                kind = NotificationTypes.Warning
+            else:
+                timeout = None
+                kind = NotificationTypes.Information
+            self.ui.showNotification(
+                UI.PixmapCache.getPixmap("debug48"),
+                self.tr("Program terminated"), msg, kind=kind,
+                timeout=timeout)
+    
+    def __lastClientExited(self):
+        """
+        Private slot handling the exit of the last client.
+        """
+        self.viewmanager.exit()
+        self.__resetUI()
+    
     def __clientSyntaxError(self, message, filename, lineNo, characterNo):
         """
         Private method to handle a syntax error in the debugged program.
@@ -1122,13 +1192,19 @@
                     '</p>')
                 .format(filename, message, lineNo, characterNo))
         
-    def __clientException(self, exceptionType, exceptionMessage, stackTrace):
+    def __clientException(self, exceptionType, exceptionMessage, stackTrace,
+                          debuggerId):
         """
         Private method to handle an exception of the debugged program.
         
-        @param exceptionType type of exception raised (string)
-        @param exceptionMessage message given by the exception (string)
-        @param stackTrace list of stack entries (list of string)
+        @param exceptionType type of exception raised
+        @type str
+        @param exceptionMessage message given by the exception
+        @type (str
+        @param stackTrace list of stack entries
+        @type list of str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         self.ui.raise_()
         QApplication.processEvents()
@@ -1212,9 +1288,9 @@
                 stack = []
                 for fn, ln, func, args in stackTrace:
                     stack.append((fn, ln, func, args))
-                self.clientStack.emit(stack)
-                self.__getClientVariables()
-                self.__getClientDisassembly()
+                self.clientStack.emit(stack, debuggerId)
+                self.__getClientVariables(debuggerId)
+                self.__getClientDisassembly(debuggerId)
                 self.ui.setDebugProfile()
                 self.debugActGrp.setEnabled(True)
                 return
@@ -1224,13 +1300,14 @@
         
         if self.lastAction != -1:
             if self.lastAction == 2:
-                self.__specialContinue()
+                self.__specialContinue(debuggerId)
             else:
-                self.debugActions[self.lastAction]()
+                self.debugActions[self.lastAction](debuggerId)
         else:
-            self.__continue()
+            self.__continue(debuggerId)
         
-    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs):
+    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs,
+                       debuggerId):
         """
         Private method to handle a signal generated on the client side.
         
@@ -1244,6 +1321,8 @@
         @type str
         @param funcArgs function arguments
         @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         self.ui.raise_()
         self.ui.activateWindow()
@@ -1268,77 +1347,107 @@
                 self.tr('The program being debugged has terminated'
                         ' unexpectedly.'))
         
-    def __getThreadList(self):
+    def __getThreadList(self, debuggerId):
         """
         Private method to get the list of threads from the client.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debugServer.remoteThreadList()
+        self.debugServer.remoteThreadList(debuggerId)
         
-    def __clientThreadSet(self):
+    def __clientThreadSet(self, debuggerId):
         """
         Private method to handle a change of the client's current thread.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debugServer.remoteClientVariables(0, self.localsVarFilter)
-        
-    def __getClientVariables(self):
+        self.debugServer.remoteClientVariables(
+            debuggerId, 0, self.__localsVarFilterList)
+    
+    def __getClientVariables(self, debuggerId):
         """
         Private method to request the global and local variables.
         
         In the first step, the global variables are requested from the client.
         Once these have been received, the local variables are requested.
         This happens in the method '__clientVariables'.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         # get globals first
-        self.debugServer.remoteClientVariables(1, self.globalsVarFilter)
+        self.debugServer.remoteClientVariables(
+            debuggerId, 1, self.__globalsVarFilterList)
         # the local variables are requested once we have received the globals
         
-    def __clientVariables(self, scope, variables):
+    def __clientVariables(self, scope, variables, debuggerId):
         """
         Private method to write the clients variables to the user interface.
         
-        @param scope scope of the variables (-1 = empty global, 1 = global,
+        @param scope scope of the variables (-1 = empty locals, 1 = global,
             0 = local)
+        @type int
         @param variables the list of variables from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.ui.activateDebugViewer()
-        if scope > 0:
-            self.debugViewer.showVariables(variables, True)
-            if scope == 1:
-                # now get the local variables
-                self.debugServer.remoteClientVariables(0, self.localsVarFilter)
-        elif scope == 0:
-            self.debugViewer.showVariables(variables, False)
-        elif scope == -1:
-            vlist = [(self.tr('No locals available.'), '', '')]
-            self.debugViewer.showVariables(vlist, False)
+        if debuggerId == self.getSelectedDebuggerId():
+            self.ui.activateDebugViewer()
+            if scope > 0:
+                self.debugViewer.showVariables(variables, True)
+                if scope == 1:
+                    # now get the local variables
+                    self.debugServer.remoteClientVariables(
+                        self.getSelectedDebuggerId(),
+                        0, self.__localsVarFilterList)
+            elif scope == 0:
+                self.debugViewer.showVariables(variables, False)
+            elif scope == -1:
+                vlist = [(self.tr('No locals available.'), '', '')]
+                self.debugViewer.showVariables(vlist, False)
         
-    def __clientVariable(self, scope, variables):
+    def __clientVariable(self, scope, variables, debuggerId):
         """
         Private method to write the contents of a clients classvariable to
         the user interface.
         
-        @param scope scope of the variables (-1 = empty global, 1 = global,
+        @param scope scope of the variables (-1 = empty locals, 1 = global,
             0 = local)
-        @param variables the list of members of a classvariable from the client
+        @type int
+        @param variables the list of variables from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.ui.activateDebugViewer()
-        if scope == 1:
-            self.debugViewer.showVariable(variables, True)
-        elif scope == 0:
-            self.debugViewer.showVariable(variables, False)
+        if debuggerId == self.getSelectedDebuggerId():
+            self.ui.activateDebugViewer()
+            if scope == 1:
+                self.debugViewer.showVariable(variables, True)
+            elif scope == 0:
+                self.debugViewer.showVariable(variables, False)
     
-    def __getClientDisassembly(self):
+    def __getClientDisassembly(self, debuggerId):
         """
         Private method to ask the client for the latest traceback disassembly.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debugServer.remoteClientDisassembly()
+        self.debugServer.remoteClientDisassembly(debuggerId)
     
-    def __clientBreakConditionError(self, filename, lineno):
+    def __clientBreakConditionError(self, filename, lineno, debuggerId):
         """
         Private method to handle a condition error of a breakpoint.
         
-        @param filename filename of the breakpoint (string)
-        @param lineno linenumber of the breakpoint (integer)
+        @param filename filename of the breakpoint
+        @type str
+        @param lineno line umber of the breakpoint
+        @type int
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         E5MessageBox.critical(
             self.ui,
@@ -1368,13 +1477,16 @@
             model.setBreakPointByIndex(index, fn, line,
                                        (cond, temp, enabled, count))
         
-    def __clientWatchConditionError(self, cond):
+    def __clientWatchConditionError(self, cond, debuggerId):
         """
         Private method to handle a expression error of a watch expression.
         
         Note: This can only happen for normal watch expressions
         
-        @param cond expression of the watch expression (string)
+        @param cond expression of the watch expression
+        @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         E5MessageBox.critical(
             self.ui,
@@ -1431,11 +1543,14 @@
         """
         from .VariablesFilterDialog import VariablesFilterDialog
         dlg = VariablesFilterDialog(self.ui, 'Filter Dialog', True)
-        dlg.setSelection(self.localsVarFilter, self.globalsVarFilter)
+        dlg.setSelection(self.__localsVarFilterList,
+                         self.__globalsVarFilterList)
         if dlg.exec() == QDialog.Accepted:
-            self.localsVarFilter, self.globalsVarFilter = dlg.getSelection()
+            self.__localsVarFilterList, self.__globalsVarFilterList = (
+                dlg.getSelection()
+            )
             self.debugViewer.setVariablesFilter(
-                self.globalsVarFilter, self.localsVarFilter)
+                self.__globalsVarFilterList, self.__localsVarFilterList)
 
     def __configureExceptionsFilter(self):
         """
@@ -1599,7 +1714,8 @@
                 # save the info for later use
                 self.project.setDbgInfo(
                     lastUsedVenvName, argv, wd, env, exceptions, self.excList,
-                    self.excIgnoreList, clearShell)
+                    self.excIgnoreList, clearShell
+                )
                 
                 self.lastStartAction = 6
                 self.clientType = self.project.getProjectLanguage()
@@ -1668,8 +1784,9 @@
             self.setArgvHistory("", clearHistories=True)
             self.setWdHistory("", clearHistories=True)
             self.setEnvHistory("", clearHistories=True)
+            self.setMultiprocessNoDebugHistory("", clearHistories=True)
         elif dlg.historiesModified():
-            argvHistory, wdHistory, envHistory = dlg.getHistories()
+            argvHistory, wdHistory, envHistory, _ = dlg.getHistories()
             self.setArgvHistory("", history=argvHistory)
             self.setWdHistory("", history=wdHistory)
             self.setEnvHistory("", history=envHistory)
@@ -1734,7 +1851,8 @@
                 # save the info for later use
                 self.project.setDbgInfo(
                     lastUsedVenvName, argv, wd, env, exceptions, self.excList,
-                    self.excIgnoreList, clearShell)
+                    self.excIgnoreList, clearShell
+                )
                 
                 self.lastStartAction = 8
                 self.clientType = self.project.getProjectLanguage()
@@ -1803,8 +1921,9 @@
             self.setArgvHistory("", clearHistories=True)
             self.setWdHistory("", clearHistories=True)
             self.setEnvHistory("", clearHistories=True)
+            self.setMultiprocessNoDebugHistory("", clearHistories=True)
         elif dlg.historiesModified():
-            argvHistory, wdHistory, envHistory = dlg.getHistories()
+            argvHistory, wdHistory, envHistory, _ = dlg.getHistories()
             self.setArgvHistory("", history=argvHistory)
             self.setWdHistory("", history=wdHistory)
             self.setEnvHistory("", history=envHistory)
@@ -1843,13 +1962,10 @@
         dlg = StartDialog(
             cap, self.lastUsedVenvName, self.argvHistory, self.wdHistory,
             self.envHistory, self.exceptions, self.ui, 1,
-            autoClearShell=self.autoClearShell,
-            autoFork=self.forkAutomatically,
-            forkChild=self.forkIntoChild)
+            autoClearShell=self.autoClearShell)
         if dlg.exec() == QDialog.Accepted:
             (lastUsedVenvName, argv, wd, env, exceptions, clearShell,
              console) = dlg.getData()
-            forkAutomatically, forkIntoChild = dlg.getRunData()
             
             if runProject:
                 fn = self.project.getMainScript(True)
@@ -1871,7 +1987,8 @@
                 # save the info for later use
                 self.project.setDbgInfo(
                     lastUsedVenvName, argv, wd, env, exceptions, self.excList,
-                    self.excIgnoreList, clearShell)
+                    self.excIgnoreList, clearShell
+                )
                 
                 self.lastStartAction = 4
                 self.clientType = self.project.getProjectLanguage()
@@ -1914,10 +2031,6 @@
             # Save the run in console flag
             self.runInConsole = console
             
-            # Save the forking flags
-            self.forkAutomatically = forkAutomatically
-            self.forkIntoChild = forkIntoChild
-            
             # Hide all error highlights
             self.viewmanager.unhighlight()
             
@@ -1933,8 +2046,7 @@
                 self.debugServer.remoteRun(
                     lastUsedVenvName, fn, argv, wd, env,
                     autoClearShell=self.autoClearShell, forProject=runProject,
-                    runInConsole=console, autoFork=forkAutomatically,
-                    forkChild=forkIntoChild, clientType=self.clientType)
+                    runInConsole=console, clientType=self.clientType)
                 
                 self.stopAct.setEnabled(True)
         
@@ -1942,8 +2054,9 @@
             self.setArgvHistory("", clearHistories=True)
             self.setWdHistory("", clearHistories=True)
             self.setEnvHistory("", clearHistories=True)
+            self.setMultiprocessNoDebugHistory("", clearHistories=True)
         elif dlg.historiesModified():
-            argvHistory, wdHistory, envHistory = dlg.getHistories()
+            argvHistory, wdHistory, envHistory, _ = dlg.getHistories()
             self.setArgvHistory("", history=argvHistory)
             self.setWdHistory("", history=wdHistory)
             self.setEnvHistory("", history=envHistory)
@@ -1983,13 +2096,14 @@
             cap, self.lastUsedVenvName, self.argvHistory, self.wdHistory,
             self.envHistory, self.exceptions, self.ui, 0,
             tracePython=self.tracePython, autoClearShell=self.autoClearShell,
-            autoContinue=self.autoContinue, autoFork=self.forkAutomatically,
-            forkChild=self.forkIntoChild)
+            autoContinue=self.autoContinue,
+            enableMultiprocess=self.enableMultiprocess,
+            multiprocessNoDebugHistory=self.multiprocessNoDebugHistory)
         if dlg.exec() == QDialog.Accepted:
             (lastUsedVenvName, argv, wd, env, exceptions, clearShell,
              console) = dlg.getData()
-            tracePython, autoContinue, forkAutomatically, forkIntoChild = (
-                dlg.getDebugData())
+            (tracePython, autoContinue, enableMultiprocess,
+             multiprocessNoDebug) = dlg.getDebugData()
             
             if debugProject:
                 fn = self.project.getMainScript(True)
@@ -2012,7 +2126,10 @@
                 self.project.setDbgInfo(
                     lastUsedVenvName, argv, wd, env, exceptions, self.excList,
                     self.excIgnoreList, clearShell, tracePython=tracePython,
-                    autoContinue=self.autoContinue)
+                    autoContinue=autoContinue,
+                    enableMultiprocess=enableMultiprocess,
+                    multiprocessNoDebug=multiprocessNoDebug
+                )
                 
                 self.lastStartAction = 2
                 self.clientType = self.project.getProjectLanguage()
@@ -2060,9 +2177,9 @@
             # Save the auto continue flag
             self.autoContinue = autoContinue
             
-            # Save the forking flags
-            self.forkAutomatically = forkAutomatically
-            self.forkIntoChild = forkIntoChild
+            # Save the multiprocess debugging data
+            self.enableMultiprocess = enableMultiprocess
+            self.setMultiprocessNoDebugHistory(multiprocessNoDebug)
             
             # Hide all error highlights
             self.viewmanager.unhighlight()
@@ -2087,9 +2204,10 @@
                     autoClearShell=self.autoClearShell,
                     tracePython=tracePython,
                     autoContinue=autoContinue, forProject=debugProject,
-                    runInConsole=console, autoFork=forkAutomatically,
-                    forkChild=forkIntoChild, clientType=self.clientType,
-                    enableCallTrace=enableCallTrace)
+                    runInConsole=console, clientType=self.clientType,
+                    enableCallTrace=enableCallTrace,
+                    enableMultiprocess=enableMultiprocess,
+                    multiprocessNoDebug=multiprocessNoDebug)
                 
                 if (
                     self.debugServer.isClientProcessUp() and
@@ -2104,11 +2222,14 @@
             self.setArgvHistory("", clearHistories=True)
             self.setWdHistory("", clearHistories=True)
             self.setEnvHistory("", clearHistories=True)
+            self.setMultiprocessNoDebugHistory("", clearHistories=True)
         elif dlg.historiesModified():
-            argvHistory, wdHistory, envHistory = dlg.getHistories()
+            (argvHistory, wdHistory, envHistory,
+             noDebugHistory) = dlg.getHistories()
             self.setArgvHistory("", history=argvHistory)
             self.setWdHistory("", history=wdHistory)
             self.setEnvHistory("", history=envHistory)
+            self.setMultiprocessNoDebugHistory("", history=noDebugHistory)
     
     def __doRestart(self):
         """
@@ -2162,6 +2283,7 @@
                 enableCallTrace = self.debugViewer.isCallTraceEnabled()
                 self.debugViewer.clearCallTrace()
                 self.debugViewer.setCallTraceToProjectMode(forProject)
+                multiprocessNoDebug = self.multiprocessNoDebugHistory[0]
                 
                 # Ask the client to debug the new program.
                 self.debugServer.remoteLoad(
@@ -2171,10 +2293,10 @@
                     autoContinue=self.autoContinue,
                     forProject=forProject,
                     runInConsole=self.runInConsole,
-                    autoFork=self.forkAutomatically,
-                    forkChild=self.forkIntoChild,
                     clientType=self.clientType,
-                    enableCallTrace=enableCallTrace)
+                    enableCallTrace=enableCallTrace,
+                    enableMultiprocess=self.enableMultiprocess,
+                    multiprocessNoDebug=multiprocessNoDebug)
                 
                 # Signal that we have started a debugging session
                 self.debuggingStarted.emit(fn)
@@ -2186,8 +2308,6 @@
                     autoClearShell=self.autoClearShell,
                     forProject=forProject,
                     runInConsole=self.runInConsole,
-                    autoFork=self.forkAutomatically,
-                    forkChild=self.forkIntoChild,
                     clientType=self.clientType)
             
             elif self.lastStartAction in [5, 6]:
@@ -2240,75 +2360,140 @@
         # Initialize the call stack viewer
         self.debugViewer.initCallStackViewer(False)
         
-    def __continue(self):
+    def __continue(self, debuggerId=""):
         """
         Private method to handle the Continue action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 0
         self.__enterRemote()
-        self.debugServer.remoteContinue()
+        self.debugServer.remoteContinue(debuggerId)
 
-    def __specialContinue(self):
+    def __specialContinue(self, debuggerId=""):
         """
         Private method to handle the Special Continue action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 2
         self.__enterRemote()
-        self.debugServer.remoteContinue(1)
+        self.debugServer.remoteContinue(debuggerId, 1)
 
-    def __step(self):
+    def __step(self, debuggerId=""):
         """
         Private method to handle the Step action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 1
         self.__enterRemote()
-        self.debugServer.remoteStep()
+        self.debugServer.remoteStep(debuggerId)
 
-    def __stepOver(self):
+    def __stepOver(self, debuggerId=""):
         """
         Private method to handle the Step Over action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 2
         self.__enterRemote()
-        self.debugServer.remoteStepOver()
+        self.debugServer.remoteStepOver(debuggerId)
 
-    def __stepOut(self):
+    def __stepOut(self, debuggerId=""):
         """
         Private method to handle the Step Out action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 3
         self.__enterRemote()
-        self.debugServer.remoteStepOut()
+        self.debugServer.remoteStepOut(debuggerId)
 
-    def __stepQuit(self):
+    def __stepQuit(self, debuggerId=""):
         """
         Private method to handle the Step Quit action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 4
         self.__enterRemote()
-        self.debugServer.remoteStepQuit()
+        self.debugServer.remoteStepQuit(debuggerId)
         self.__resetUI()
 
-    def __runToCursor(self):
+    def __runToCursor(self, debuggerId=""):
         """
         Private method to handle the Run to Cursor action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 0
         aw = self.viewmanager.activeWindow()
         line = aw.getCursorPosition()[0] + 1
         self.__enterRemote()
         self.debugServer.remoteBreakpoint(
+            self.getSelectedDebuggerId(),
             aw.getFileName(), line, 1, None, 1)
-        self.debugServer.remoteContinue()
+        self.debugServer.remoteContinue(debuggerId)
 
-    def __moveInstructionPointer(self):
+    def __runUntil(self, debuggerId=""):
         """
-        Private method to move the instruction pointer to a different line.
+        Private method to handle the Run Until action.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
         self.lastAction = 0
         aw = self.viewmanager.activeWindow()
         line = aw.getCursorPosition()[0] + 1
-        self.debugServer.remoteMoveIP(line)
+        self.__enterRemote()
+        self.debugServer.remoteContinueUntil(debuggerId, line)
+
+    def __moveInstructionPointer(self, debuggerId=""):
+        """
+        Private method to move the instruction pointer to a different line.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        if not debuggerId:
+            debuggerId = self.getSelectedDebuggerId()
+        
+        self.lastAction = 0
+        aw = self.viewmanager.activeWindow()
+        line = aw.getCursorPosition()[0] + 1
+        self.debugServer.remoteMoveIP(debuggerId, line)
 
     def __enterRemote(self):
         """
@@ -2329,3 +2514,53 @@
         @return list of all actions (list of E5Action)
         """
         return self.actions[:]
+    
+    def getSelectedDebuggerId(self):
+        """
+        Public method to get the currently selected debugger ID.
+        
+        @return selected debugger ID
+        @rtype str
+        """
+        return self.debugViewer.getSelectedDebuggerId()
+    
+    def setDebugActionsEnabled(self, enable):
+        """
+        Public method to set the enabled state of the debug actions.
+        
+        @param enable enable state to be set
+        @type bool
+        """
+        self.debugActGrp.setEnabled(enable)
+
+    def setMultiprocessNoDebugHistory(self, noDebugList, clearHistories=False,
+                                      history=None):
+        """
+        Public slot to initialize the no debug list history.
+        
+        @param noDebugList whitespace separated list of programs not to be
+            debugged
+        @type str
+        @param clearHistories flag indicating, that the list should be cleared
+        @type bool
+        @param history list of history entries to be set
+        @type list of str
+        """
+        if clearHistories:
+            del self.multiprocessNoDebugHistory[1:]
+        elif history is not None:
+            self.multiprocessNoDebugHistory = history[:]
+        else:
+            if noDebugList in self.multiprocessNoDebugHistory:
+                self.multiprocessNoDebugHistory.remove(noDebugList)
+            self.multiprocessNoDebugHistory.insert(0, noDebugList)
+    
+    def setEnableMultiprocess(self, enableMultiprocess):
+        """
+        Public slot to initialize the enableMultiprocess flag.
+        
+        @param enableMultiprocess flag indicating, that the debugger should be
+            run in multi process mode
+        @type bool
+        """
+        self.enableMultiprocess = enableMultiprocess

eric ide

mercurial