src/eric7/Debugger/DebuggerInterfacePython.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9192
a763d57e23bc
parent 9221
bf71ee032bb4
child 9371
1da8bc75946f
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Python3 debugger interface for the debug server.
8 """
9
10 import sys
11 import os
12 import logging
13 import shlex
14 import contextlib
15
16 from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer
17
18 from EricWidgets.EricApplication import ericApp
19 from EricWidgets import EricMessageBox
20
21 from . import DebugClientCapabilities
22
23 import Globals
24 import Preferences
25 import Utilities
26
27 from eric7config import getConfig
28
29
30 ClientDefaultCapabilities = DebugClientCapabilities.HasAll
31
32
33 class DebuggerInterfacePython(QObject):
34 """
35 Class implementing the debugger interface for the debug server for
36 Python 3.
37 """
38
39 def __init__(self, debugServer, passive):
40 """
41 Constructor
42
43 @param debugServer reference to the debug server
44 @type DebugServer
45 @param passive flag indicating passive connection mode
46 @type bool
47 """
48 super().__init__()
49
50 self.__isNetworked = True
51 self.__autoContinue = False
52 self.__autoContinued = []
53 self.__isStepCommand = False
54
55 self.debugServer = debugServer
56 self.passive = passive
57 self.process = None
58 self.__startedVenv = ""
59
60 self.queue = []
61 self.__master = None
62 self.__connections = {}
63 self.__pendingConnections = []
64 self.__inShutdown = False
65
66 # set default values for capabilities of clients
67 self.clientCapabilities = ClientDefaultCapabilities
68
69 # set translation function
70 self.translate = self.__identityTranslation
71
72 if passive:
73 # set translation function
74 if Preferences.getDebugger("PathTranslation"):
75 self.translateRemote = Preferences.getDebugger("PathTranslationRemote")
76 self.translateRemoteWindows = "\\" in self.translateRemote
77 self.translateLocal = Preferences.getDebugger("PathTranslationLocal")
78 self.translateLocalWindows = "\\" in self.translateLocal
79 self.translate = self.__remoteTranslation
80 else:
81 self.translate = self.__identityTranslation
82
83 # attribute to remember the name of the executed script
84 self.__scriptName = ""
85
86 def __identityTranslation(self, fn, remote2local=True):
87 """
88 Private method to perform the identity path translation.
89
90 @param fn filename to be translated
91 @type str
92 @param remote2local flag indicating the direction of translation
93 (False = local to remote, True = remote to local [default])
94 @type bool
95 @return translated filename
96 @rtype str
97 """
98 return fn
99
100 def __remoteTranslation(self, fn, remote2local=True):
101 """
102 Private method to perform the path translation.
103
104 @param fn filename to be translated
105 @type str
106 @param remote2local flag indicating the direction of translation
107 (False = local to remote, True = remote to local [default])
108 @type bool
109 @return translated filename
110 @rtype str
111 """
112 if remote2local:
113 path = fn.replace(self.translateRemote, self.translateLocal)
114 if self.translateLocalWindows:
115 path = path.replace("/", "\\")
116 else:
117 path = fn.replace(self.translateLocal, self.translateRemote)
118 if not self.translateRemoteWindows:
119 path = path.replace("\\", "/")
120
121 return path
122
123 def __startProcess(self, program, arguments, environment=None, workingDir=None):
124 """
125 Private method to start the debugger client process.
126
127 @param program name of the executable to start
128 @type str
129 @param arguments arguments to be passed to the program
130 @type list of str
131 @param environment dictionary of environment settings to pass
132 @type dict of str
133 @param workingDir directory to start the debugger client in
134 @type str
135 @return the process object
136 @rtype QProcess or None
137 """
138 proc = QProcess(self)
139 if environment is not None:
140 env = QProcessEnvironment()
141 for key, value in list(environment.items()):
142 env.insert(key, value)
143 proc.setProcessEnvironment(env)
144 args = arguments[:]
145 if workingDir:
146 proc.setWorkingDirectory(workingDir)
147 proc.start(program, args)
148 if not proc.waitForStarted(10000):
149 proc = None
150
151 return proc
152
153 def startRemote(
154 self,
155 port,
156 runInConsole,
157 venvName,
158 originalPathString,
159 workingDir=None,
160 configOverride=None,
161 ):
162 """
163 Public method to start a remote Python interpreter.
164
165 @param port port number the debug server is listening on
166 @type int
167 @param runInConsole flag indicating to start the debugger in a
168 console window
169 @type bool
170 @param venvName name of the virtual environment to be used
171 @type str
172 @param originalPathString original PATH environment variable
173 @type str
174 @param workingDir directory to start the debugger client in
175 @type str
176 @param configOverride dictionary containing the global config override
177 data
178 @type dict
179 @return client process object, a flag to indicate a network connection
180 and the name of the interpreter in case of a local execution
181 @rtype tuple of (QProcess, bool, str)
182 """
183 global origPathEnv
184
185 if not venvName:
186 venvName = Preferences.getDebugger("Python3VirtualEnv")
187 venvManager = ericApp().getObject("VirtualEnvManager")
188 interpreter = venvManager.getVirtualenvInterpreter(venvName)
189 execPath = venvManager.getVirtualenvExecPath(venvName)
190 if interpreter == "":
191 # use the interpreter used to run eric for identical variants
192 interpreter = Globals.getPythonExecutable()
193 if interpreter == "":
194 EricMessageBox.critical(
195 None,
196 self.tr("Start Debugger"),
197 self.tr("""<p>No suitable Python3 environment configured.</p>"""),
198 )
199 return None, False, ""
200
201 self.__inShutdown = False
202
203 debugClientType = Preferences.getDebugger("DebugClientType3")
204 if debugClientType == "standard":
205 debugClient = os.path.join(
206 getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py"
207 )
208 else:
209 debugClient = Preferences.getDebugger("DebugClient3")
210 if debugClient == "":
211 debugClient = os.path.join(
212 sys.path[0], "DebugClients", "Python", "DebugClient.py"
213 )
214
215 redirect = (
216 str(configOverride["redirect"])
217 if configOverride and configOverride["enable"]
218 else str(Preferences.getDebugger("Python3Redirect"))
219 )
220 noencoding = (
221 Preferences.getDebugger("Python3NoEncoding") and "--no-encoding" or ""
222 )
223 multiprocessEnabled = (
224 "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else ""
225 )
226
227 if Preferences.getDebugger("RemoteDbgEnabled"):
228 ipaddr = self.debugServer.getHostAddress(False)
229 rexec = Preferences.getDebugger("RemoteExecution")
230 rhost = Preferences.getDebugger("RemoteHost")
231 if rhost == "":
232 rhost = "localhost"
233 if rexec:
234 args = Utilities.parseOptionString(rexec) + [
235 rhost,
236 interpreter,
237 debugClient,
238 ]
239 if noencoding:
240 args.append(noencoding)
241 if multiprocessEnabled:
242 args.append(multiprocessEnabled)
243 args.extend([str(port), redirect, ipaddr])
244 if Utilities.isWindowsPlatform():
245 if not os.path.splitext(args[0])[1]:
246 for ext in [".exe", ".com", ".cmd", ".bat"]:
247 prog = Utilities.getExecutablePath(args[0] + ext)
248 if prog:
249 args[0] = prog
250 break
251 else:
252 args[0] = Utilities.getExecutablePath(args[0])
253 process = self.__startProcess(args[0], args[1:], workingDir=workingDir)
254 if process is None:
255 EricMessageBox.critical(
256 None,
257 self.tr("Start Debugger"),
258 self.tr(
259 """<p>The debugger backend could not be"""
260 """ started.</p>"""
261 ),
262 )
263
264 # set translation function
265 if Preferences.getDebugger("PathTranslation"):
266 self.translateRemote = Preferences.getDebugger(
267 "PathTranslationRemote"
268 )
269 self.translateRemoteWindows = "\\" in self.translateRemote
270 self.translateLocal = Preferences.getDebugger(
271 "PathTranslationLocal"
272 )
273 self.translate = self.__remoteTranslation
274 self.translateLocalWindows = "\\" in self.translateLocal
275 else:
276 self.translate = self.__identityTranslation
277 return process, self.__isNetworked, ""
278
279 # set translation function
280 self.translate = self.__identityTranslation
281
282 # setup the environment for the debugger
283 if Preferences.getDebugger("DebugEnvironmentReplace"):
284 clientEnv = {}
285 else:
286 clientEnv = os.environ.copy()
287 if originalPathString:
288 clientEnv["PATH"] = originalPathString
289 envlist = shlex.split(Preferences.getDebugger("DebugEnvironment"))
290 for el in envlist:
291 with contextlib.suppress(ValueError):
292 key, value = el.split("=", 1)
293 clientEnv[str(key)] = str(value)
294 if execPath:
295 if "PATH" in clientEnv:
296 clientEnv["PATH"] = os.pathsep.join([execPath, clientEnv["PATH"]])
297 else:
298 clientEnv["PATH"] = execPath
299
300 ipaddr = self.debugServer.getHostAddress(True)
301 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"):
302 ccmd = Preferences.getDebugger("ConsoleDbgCommand")
303 if ccmd:
304 args = Utilities.parseOptionString(ccmd) + [
305 interpreter,
306 os.path.abspath(debugClient),
307 ]
308 if noencoding:
309 args.append(noencoding)
310 if multiprocessEnabled:
311 args.append(multiprocessEnabled)
312 args.extend([str(port), "0", ipaddr])
313 args[0] = Utilities.getExecutablePath(args[0])
314 process = self.__startProcess(
315 args[0], args[1:], clientEnv, workingDir=workingDir
316 )
317 if process is None:
318 EricMessageBox.critical(
319 None,
320 self.tr("Start Debugger"),
321 self.tr(
322 """<p>The debugger backend could not be"""
323 """ started.</p>"""
324 ),
325 )
326 return process, self.__isNetworked, interpreter
327
328 args = [debugClient]
329 if noencoding:
330 args.append(noencoding)
331 if multiprocessEnabled:
332 args.append(multiprocessEnabled)
333 args.extend([str(port), redirect, ipaddr])
334 process = self.__startProcess(
335 interpreter, args, clientEnv, workingDir=workingDir
336 )
337 if process is None:
338 self.__startedVenv = ""
339 EricMessageBox.critical(
340 None,
341 self.tr("Start Debugger"),
342 self.tr("""<p>The debugger backend could not be started.</p>"""),
343 )
344 else:
345 self.__startedVenv = venvName
346
347 return process, self.__isNetworked, interpreter
348
349 def startRemoteForProject(
350 self,
351 port,
352 runInConsole,
353 venvName,
354 originalPathString,
355 workingDir=None,
356 configOverride=None,
357 ):
358 """
359 Public method to start a remote Python interpreter for a project.
360
361 @param port port number the debug server is listening on
362 @type int
363 @param runInConsole flag indicating to start the debugger in a
364 console window
365 @type bool
366 @param venvName name of the virtual environment to be used
367 @type str
368 @param originalPathString original PATH environment variable
369 @type str
370 @param workingDir directory to start the debugger client in
371 @type str
372 @param configOverride dictionary containing the global config override
373 data
374 @type dict
375 @return client process object, a flag to indicate a network connection
376 and the name of the interpreter in case of a local execution
377 @rtype tuple of (QProcess, bool, str)
378 """
379 global origPathEnv
380
381 project = ericApp().getObject("Project")
382 if not project.isDebugPropertiesLoaded():
383 return None, self.__isNetworked, ""
384
385 # start debugger with project specific settings
386 debugClient = project.getDebugProperty("DEBUGCLIENT")
387
388 redirect = (
389 str(configOverride["redirect"])
390 if configOverride and configOverride["enable"]
391 else str(project.getDebugProperty("REDIRECT"))
392 )
393 noencoding = "--no-encoding" if project.getDebugProperty("NOENCODING") else ""
394 multiprocessEnabled = (
395 "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else ""
396 )
397
398 if venvName:
399 venvManager = ericApp().getObject("VirtualEnvManager")
400 interpreter = venvManager.getVirtualenvInterpreter(venvName)
401 execPath = venvManager.getVirtualenvExecPath(venvName)
402 else:
403 venvName = project.getProjectVenv()
404 execPath = project.getProjectExecPath()
405 interpreter = project.getProjectInterpreter()
406 if interpreter == "":
407 EricMessageBox.critical(
408 None,
409 self.tr("Start Debugger"),
410 self.tr("""<p>No suitable Python3 environment configured.</p>"""),
411 )
412 return None, self.__isNetworked, ""
413
414 self.__inShutdown = False
415
416 if project.getDebugProperty("REMOTEDEBUGGER"):
417 ipaddr = self.debugServer.getHostAddress(False)
418 rexec = project.getDebugProperty("REMOTECOMMAND")
419 rhost = project.getDebugProperty("REMOTEHOST")
420 if rhost == "":
421 rhost = "localhost"
422 if rexec:
423 args = Utilities.parseOptionString(rexec) + [
424 rhost,
425 interpreter,
426 debugClient,
427 ]
428 if noencoding:
429 args.append(noencoding)
430 if multiprocessEnabled:
431 args.append(multiprocessEnabled)
432 args.extend([str(port), redirect, ipaddr])
433 if Utilities.isWindowsPlatform():
434 if not os.path.splitext(args[0])[1]:
435 for ext in [".exe", ".com", ".cmd", ".bat"]:
436 prog = Utilities.getExecutablePath(args[0] + ext)
437 if prog:
438 args[0] = prog
439 break
440 else:
441 args[0] = Utilities.getExecutablePath(args[0])
442 process = self.__startProcess(args[0], args[1:], workingDir=workingDir)
443 if process is None:
444 EricMessageBox.critical(
445 None,
446 self.tr("Start Debugger"),
447 self.tr(
448 """<p>The debugger backend could not be"""
449 """ started.</p>"""
450 ),
451 )
452 # set translation function
453 if project.getDebugProperty("PATHTRANSLATION"):
454 self.translateRemote = project.getDebugProperty("REMOTEPATH")
455 self.translateRemoteWindows = "\\" in self.translateRemote
456 self.translateLocal = project.getDebugProperty("LOCALPATH")
457 self.translateLocalWindows = "\\" in self.translateLocal
458 self.translate = self.__remoteTranslation
459 else:
460 self.translate = self.__identityTranslation
461 return process, self.__isNetworked, ""
462 else:
463 # remote shell command is missing
464 return None, self.__isNetworked, ""
465
466 # set translation function
467 self.translate = self.__identityTranslation
468
469 # setup the environment for the debugger
470 if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
471 clientEnv = {}
472 else:
473 clientEnv = os.environ.copy()
474 if originalPathString:
475 clientEnv["PATH"] = originalPathString
476 envlist = shlex.split(project.getDebugProperty("ENVIRONMENTSTRING"))
477 for el in envlist:
478 with contextlib.suppress(ValueError):
479 key, value = el.split("=", 1)
480 clientEnv[str(key)] = str(value)
481 if execPath:
482 if "PATH" in clientEnv:
483 clientEnv["PATH"] = os.pathsep.join([execPath, clientEnv["PATH"]])
484 else:
485 clientEnv["PATH"] = execPath
486
487 ipaddr = self.debugServer.getHostAddress(True)
488 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"):
489 ccmd = project.getDebugProperty(
490 "CONSOLECOMMAND"
491 ) or Preferences.getDebugger("ConsoleDbgCommand")
492 if ccmd:
493 args = Utilities.parseOptionString(ccmd) + [
494 interpreter,
495 os.path.abspath(debugClient),
496 ]
497 if noencoding:
498 args.append(noencoding)
499 if multiprocessEnabled:
500 args.append(multiprocessEnabled)
501 args.extend([str(port), "0", ipaddr])
502 args[0] = Utilities.getExecutablePath(args[0])
503 process = self.__startProcess(
504 args[0], args[1:], clientEnv, workingDir=workingDir
505 )
506 if process is None:
507 EricMessageBox.critical(
508 None,
509 self.tr("Start Debugger"),
510 self.tr(
511 """<p>The debugger backend could not be"""
512 """ started.</p>"""
513 ),
514 )
515 return process, self.__isNetworked, interpreter
516
517 args = [debugClient]
518 if noencoding:
519 args.append(noencoding)
520 if multiprocessEnabled:
521 args.append(multiprocessEnabled)
522 args.extend([str(port), redirect, ipaddr])
523 process = self.__startProcess(
524 interpreter, args, clientEnv, workingDir=workingDir
525 )
526 if process is None:
527 self.__startedVenv = ""
528 EricMessageBox.critical(
529 None,
530 self.tr("Start Debugger"),
531 self.tr("""<p>The debugger backend could not be started.</p>"""),
532 )
533 else:
534 self.__startedVenv = venvName
535
536 return process, self.__isNetworked, interpreter
537
538 def getClientCapabilities(self):
539 """
540 Public method to retrieve the debug clients capabilities.
541
542 @return debug client capabilities
543 @rtype int
544 """
545 return self.clientCapabilities
546
547 def newConnection(self, sock):
548 """
549 Public slot to handle a new connection.
550
551 @param sock reference to the socket object
552 @type QTcpSocket
553 @return flag indicating success
554 @rtype bool
555 """
556 self.__pendingConnections.append(sock)
557
558 sock.readyRead.connect(lambda: self.__parseClientLine(sock))
559 sock.disconnected.connect(lambda: self.__socketDisconnected(sock))
560
561 return True
562
563 def __assignDebuggerId(self, sock, debuggerId):
564 """
565 Private method to set the debugger id for a recent debugger connection
566 attempt.
567
568 @param sock reference to the socket object
569 @type QTcpSocket
570 @param debuggerId id of the connected debug client
571 @type str
572 """
573 if sock in self.__pendingConnections:
574 self.__connections[debuggerId] = sock
575 self.__pendingConnections.remove(sock)
576
577 if self.__master is None:
578 self.__master = debuggerId
579 # Get the remote clients capabilities
580 self.remoteCapabilities(debuggerId)
581
582 self.debugServer.signalClientDebuggerId(debuggerId)
583
584 if debuggerId == self.__master:
585 self.__flush()
586 self.debugServer.masterClientConnected()
587
588 self.debugServer.initializeClient(debuggerId)
589
590 # perform auto-continue except for master
591 if (
592 debuggerId != self.__master
593 and self.__autoContinue
594 and not self.__isStepCommand
595 ):
596 self.__autoContinued.append(debuggerId)
597 QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId))
598
599 def __socketDisconnected(self, sock):
600 """
601 Private slot handling a socket disconnecting.
602
603 @param sock reference to the disconnected socket
604 @type QTcpSocket
605 """
606 for debuggerId in self.__connections:
607 if self.__connections[debuggerId] is sock:
608 del self.__connections[debuggerId]
609 if debuggerId == self.__master:
610 self.__master = None
611 if debuggerId in self.__autoContinued:
612 self.__autoContinued.remove(debuggerId)
613 if not self.__inShutdown:
614 with contextlib.suppress(RuntimeError):
615 # can be ignored during a shutdown
616 self.debugServer.signalClientDisconnected(debuggerId)
617 break
618 else:
619 if sock in self.__pendingConnections:
620 self.__pendingConnections.remove(sock)
621
622 if not self.__connections:
623 # no active connections anymore
624 with contextlib.suppress(RuntimeError):
625 self.debugServer.signalLastClientExited()
626 # debug server object might have been deleted already
627 # ignore this
628 self.__autoContinued.clear()
629 self.debugServer.startClient()
630
631 def getDebuggerIds(self):
632 """
633 Public method to return the IDs of the connected debugger backends.
634
635 @return list of connected debugger backend IDs
636 @rtype list of str
637 """
638 return sorted(self.__connections.keys())
639
640 def __flush(self):
641 """
642 Private slot to flush the queue.
643 """
644 if self.__master:
645 # Send commands that were waiting for the connection.
646 for cmd in self.queue:
647 self.__writeJsonCommandToSocket(cmd, self.__connections[self.__master])
648
649 self.queue = []
650
651 def shutdown(self):
652 """
653 Public method to cleanly shut down.
654
655 It closes our sockets and shuts down the debug clients.
656 (Needed on Win OS)
657 """
658 if not self.__master:
659 return
660
661 self.__inShutdown = True
662
663 while self.__connections:
664 debuggerId, sock = self.__connections.popitem()
665 self.__shutdownSocket(sock)
666
667 while self.__pendingConnections:
668 sock = self.__pendingConnections.pop()
669 self.__shutdownSocket(sock)
670
671 # reinitialize
672 self.queue = []
673
674 self.__master = None
675
676 def __shutdownSocket(self, sock):
677 """
678 Private slot to shut down a socket.
679
680 @param sock reference to the socket
681 @type QTcpSocket
682 """
683 # do not want any slots called during shutdown
684 sock.readyRead.disconnect()
685 sock.disconnected.disconnect()
686
687 # close down socket, and shut down client as well.
688 self.__sendJsonCommand("RequestShutdown", {}, sock=sock)
689 sock.flush()
690 sock.close()
691
692 sock.setParent(None)
693 sock.deleteLater()
694 del sock
695
696 def isConnected(self):
697 """
698 Public method to test, if a debug client has connected.
699
700 @return flag indicating the connection status
701 @rtype bool
702 """
703 return bool(self.__connections)
704
705 def remoteEnvironment(self, env):
706 """
707 Public method to set the environment for a program to debug, run, ...
708
709 @param env environment settings
710 @type dict
711 """
712 self.__sendJsonCommand(
713 "RequestEnvironment", {"environment": env}, self.__master
714 )
715
716 def remoteLoad(
717 self,
718 fn,
719 argv,
720 wd,
721 traceInterpreter=False,
722 autoContinue=True,
723 enableMultiprocess=False,
724 ):
725 """
726 Public method to load a new program to debug.
727
728 @param fn the filename to debug
729 @type str
730 @param argv the commandline arguments to pass to the program
731 @type str
732 @param wd the working directory for the program
733 @type str
734 @param traceInterpreter flag indicating if the interpreter library
735 should be traced as well
736 @type bool
737 @param autoContinue flag indicating, that the debugger should not
738 stop at the first executable line
739 @type bool
740 @param enableMultiprocess flag indicating to perform multiprocess
741 debugging
742 @type bool
743 """
744 self.__autoContinue = autoContinue
745 self.__scriptName = os.path.abspath(fn)
746 self.__isStepCommand = False
747
748 wd = self.translate(wd, False)
749 fn = self.translate(os.path.abspath(fn), False)
750 self.__sendJsonCommand(
751 "RequestLoad",
752 {
753 "workdir": wd,
754 "filename": fn,
755 "argv": Utilities.parseOptionString(argv),
756 "traceInterpreter": traceInterpreter,
757 "multiprocess": enableMultiprocess,
758 },
759 self.__master,
760 )
761
762 def remoteRun(self, fn, argv, wd):
763 """
764 Public method to load a new program to run.
765
766 @param fn the filename to run
767 @type str
768 @param argv the commandline arguments to pass to the program
769 @type str
770 @param wd the working directory for the program
771 @type str
772 """
773 self.__scriptName = os.path.abspath(fn)
774
775 wd = self.translate(wd, False)
776 fn = self.translate(os.path.abspath(fn), False)
777 self.__sendJsonCommand(
778 "RequestRun",
779 {
780 "workdir": wd,
781 "filename": fn,
782 "argv": Utilities.parseOptionString(argv),
783 },
784 self.__master,
785 )
786
787 def remoteCoverage(self, fn, argv, wd, erase=False):
788 """
789 Public method to load a new program to collect coverage data.
790
791 @param fn the filename to run
792 @type str
793 @param argv the commandline arguments to pass to the program
794 @type str
795 @param wd the working directory for the program
796 @type str
797 @param erase flag indicating that coverage info should be
798 cleared first
799 @type bool
800 """
801 self.__scriptName = os.path.abspath(fn)
802
803 wd = self.translate(wd, False)
804 fn = self.translate(os.path.abspath(fn), False)
805 self.__sendJsonCommand(
806 "RequestCoverage",
807 {
808 "workdir": wd,
809 "filename": fn,
810 "argv": Utilities.parseOptionString(argv),
811 "erase": erase,
812 },
813 self.__master,
814 )
815
816 def remoteProfile(self, fn, argv, wd, erase=False):
817 """
818 Public method to load a new program to collect profiling data.
819
820 @param fn the filename to run
821 @type str
822 @param argv the commandline arguments to pass to the program
823 @type str
824 @param wd the working directory for the program
825 @type str
826 @param erase flag indicating that timing info should be cleared
827 first
828 @type bool
829 """
830 self.__scriptName = os.path.abspath(fn)
831
832 wd = self.translate(wd, False)
833 fn = self.translate(os.path.abspath(fn), False)
834 self.__sendJsonCommand(
835 "RequestProfile",
836 {
837 "workdir": wd,
838 "filename": fn,
839 "argv": Utilities.parseOptionString(argv),
840 "erase": erase,
841 },
842 self.__master,
843 )
844
845 def remoteStatement(self, debuggerId, stmt):
846 """
847 Public method to execute a Python statement.
848
849 @param debuggerId ID of the debugger backend
850 @type str
851 @param stmt the Python statement to execute.
852 @type str
853 """
854 self.__sendJsonCommand(
855 "ExecuteStatement",
856 {
857 "statement": stmt,
858 },
859 debuggerId,
860 )
861
862 def remoteStep(self, debuggerId):
863 """
864 Public method to single step the debugged program.
865
866 @param debuggerId ID of the debugger backend
867 @type str
868 """
869 self.__isStepCommand = True
870 self.__sendJsonCommand("RequestStep", {}, debuggerId)
871
872 def remoteStepOver(self, debuggerId):
873 """
874 Public method to step over the debugged program.
875
876 @param debuggerId ID of the debugger backend
877 @type str
878 """
879 self.__isStepCommand = True
880 self.__sendJsonCommand("RequestStepOver", {}, debuggerId)
881
882 def remoteStepOut(self, debuggerId):
883 """
884 Public method to step out the debugged program.
885
886 @param debuggerId ID of the debugger backend
887 @type str
888 """
889 self.__isStepCommand = True
890 self.__sendJsonCommand("RequestStepOut", {}, debuggerId)
891
892 def remoteStepQuit(self, debuggerId):
893 """
894 Public method to stop the debugged program.
895
896 @param debuggerId ID of the debugger backend
897 @type str
898 """
899 self.__isStepCommand = True
900 self.__sendJsonCommand("RequestStepQuit", {}, debuggerId)
901
902 def remoteContinue(self, debuggerId, special=False):
903 """
904 Public method to continue the debugged program.
905
906 @param debuggerId ID of the debugger backend
907 @type str
908 @param special flag indicating a special continue operation
909 @type bool
910 """
911 self.__isStepCommand = False
912 self.__sendJsonCommand(
913 "RequestContinue",
914 {
915 "special": special,
916 },
917 debuggerId,
918 )
919
920 def remoteContinueUntil(self, debuggerId, line):
921 """
922 Public method to continue the debugged program to the given line
923 or until returning from the current frame.
924
925 @param debuggerId ID of the debugger backend
926 @type str
927 @param line the new line, where execution should be continued to
928 @type int
929 """
930 self.__isStepCommand = False
931 self.__sendJsonCommand(
932 "RequestContinueUntil",
933 {
934 "newLine": line,
935 },
936 debuggerId,
937 )
938
939 def remoteMoveIP(self, debuggerId, line):
940 """
941 Public method to move the instruction pointer to a different line.
942
943 @param debuggerId ID of the debugger backend
944 @type str
945 @param line the new line, where execution should be continued
946 @type int
947 """
948 self.__sendJsonCommand(
949 "RequestMoveIP",
950 {
951 "newLine": line,
952 },
953 debuggerId,
954 )
955
956 def remoteBreakpoint(
957 self, debuggerId, fn, line, setBreakpoint, cond=None, temp=False
958 ):
959 """
960 Public method to set or clear a breakpoint.
961
962 @param debuggerId ID of the debugger backend
963 @type str
964 @param fn filename the breakpoint belongs to
965 @type str
966 @param line linenumber of the breakpoint
967 @type int
968 @param setBreakpoint flag indicating setting or resetting a breakpoint
969 @type bool
970 @param cond condition of the breakpoint
971 @type str
972 @param temp flag indicating a temporary breakpoint
973 @type bool
974 """
975 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
976 for debuggerId in debuggerList:
977 self.__sendJsonCommand(
978 "RequestBreakpoint",
979 {
980 "filename": self.translate(fn, False),
981 "line": line,
982 "temporary": temp,
983 "setBreakpoint": setBreakpoint,
984 "condition": cond,
985 },
986 debuggerId,
987 )
988
989 def remoteBreakpointEnable(self, debuggerId, fn, line, enable):
990 """
991 Public method to enable or disable a breakpoint.
992
993 @param debuggerId ID of the debugger backend
994 @type str
995 @param fn filename the breakpoint belongs to
996 @type str
997 @param line linenumber of the breakpoint
998 @type int
999 @param enable flag indicating enabling or disabling a breakpoint
1000 @type bool
1001 """
1002 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
1003 for debuggerId in debuggerList:
1004 self.__sendJsonCommand(
1005 "RequestBreakpointEnable",
1006 {
1007 "filename": self.translate(fn, False),
1008 "line": line,
1009 "enable": enable,
1010 },
1011 debuggerId,
1012 )
1013
1014 def remoteBreakpointIgnore(self, debuggerId, fn, line, count):
1015 """
1016 Public method to ignore a breakpoint the next couple of occurrences.
1017
1018 @param debuggerId ID of the debugger backend
1019 @type str
1020 @param fn filename the breakpoint belongs to
1021 @type str
1022 @param line linenumber of the breakpoint
1023 @type int
1024 @param count number of occurrences to ignore
1025 @type int
1026 """
1027 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
1028 for debuggerId in debuggerList:
1029 self.__sendJsonCommand(
1030 "RequestBreakpointIgnore",
1031 {
1032 "filename": self.translate(fn, False),
1033 "line": line,
1034 "count": count,
1035 },
1036 debuggerId,
1037 )
1038
1039 def remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False):
1040 """
1041 Public method to set or clear a watch expression.
1042
1043 @param debuggerId ID of the debugger backend
1044 @type str
1045 @param cond expression of the watch expression
1046 @type str
1047 @param setWatch flag indicating setting or resetting a watch expression
1048 @type bool
1049 @param temp flag indicating a temporary watch expression
1050 @type bool
1051 """
1052 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
1053 for debuggerId in debuggerList:
1054 # cond is combination of cond and special (s. watch expression
1055 # viewer)
1056 self.__sendJsonCommand(
1057 "RequestWatch",
1058 {
1059 "temporary": temp,
1060 "setWatch": setWatch,
1061 "condition": cond,
1062 },
1063 debuggerId,
1064 )
1065
1066 def remoteWatchpointEnable(self, debuggerId, cond, enable):
1067 """
1068 Public method to enable or disable a watch expression.
1069
1070 @param debuggerId ID of the debugger backend
1071 @type str
1072 @param cond expression of the watch expression
1073 @type str
1074 @param enable flag indicating enabling or disabling a watch expression
1075 @type bool
1076 """
1077 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
1078 for debuggerId in debuggerList:
1079 # cond is combination of cond and special (s. watch expression
1080 # viewer)
1081 self.__sendJsonCommand(
1082 "RequestWatchEnable",
1083 {
1084 "condition": cond,
1085 "enable": enable,
1086 },
1087 debuggerId,
1088 )
1089
1090 def remoteWatchpointIgnore(self, debuggerId, cond, count):
1091 """
1092 Public method to ignore a watch expression the next couple of
1093 occurrences.
1094
1095 @param debuggerId ID of the debugger backend
1096 @type str
1097 @param cond expression of the watch expression
1098 @type str
1099 @param count number of occurrences to ignore
1100 @type int
1101 """
1102 debuggerList = [debuggerId] if debuggerId else list(self.__connections.keys())
1103 for debuggerId in debuggerList:
1104 # cond is combination of cond and special (s. watch expression
1105 # viewer)
1106 self.__sendJsonCommand(
1107 "RequestWatchIgnore",
1108 {
1109 "condition": cond,
1110 "count": count,
1111 },
1112 debuggerId,
1113 )
1114
1115 def remoteRawInput(self, debuggerId, inputString):
1116 """
1117 Public method to send the raw input to the debugged program.
1118
1119 @param debuggerId ID of the debugger backend
1120 @type str
1121 @param inputString the raw input
1122 @type str
1123 """
1124 self.__sendJsonCommand(
1125 "RawInput",
1126 {
1127 "input": inputString,
1128 },
1129 debuggerId,
1130 )
1131
1132 def remoteThreadList(self, debuggerId):
1133 """
1134 Public method to request the list of threads from the client.
1135
1136 @param debuggerId ID of the debugger backend
1137 @type str
1138 """
1139 self.__sendJsonCommand("RequestThreadList", {}, debuggerId)
1140
1141 def remoteSetThread(self, debuggerId, tid):
1142 """
1143 Public method to request to set the given thread as current thread.
1144
1145 @param debuggerId ID of the debugger backend
1146 @type str
1147 @param tid id of the thread
1148 @type int
1149 """
1150 self.__sendJsonCommand(
1151 "RequestThreadSet",
1152 {
1153 "threadID": tid,
1154 },
1155 debuggerId,
1156 )
1157
1158 def remoteClientStack(self, debuggerId):
1159 """
1160 Public method to request the stack of the main thread.
1161
1162 @param debuggerId ID of the debugger backend
1163 @type str
1164 """
1165 self.__sendJsonCommand("RequestStack", {}, debuggerId)
1166
1167 def remoteClientVariables(
1168 self, debuggerId, scope, filterList, framenr=0, maxSize=0
1169 ):
1170 """
1171 Public method to request the variables of the debugged program.
1172
1173 @param debuggerId ID of the debugger backend
1174 @type str
1175 @param scope the scope of the variables (0 = local, 1 = global)
1176 @type int
1177 @param filterList list of variable types to filter out
1178 @type list of str
1179 @param framenr framenumber of the variables to retrieve
1180 @type int
1181 @param maxSize maximum size the formatted value of a variable will
1182 be shown. If it is bigger than that, a 'too big' indication will
1183 be given (@@TOO_BIG_TO_SHOW@@).
1184 @type int
1185 """
1186 self.__sendJsonCommand(
1187 "RequestVariables",
1188 {
1189 "frameNumber": framenr,
1190 "scope": scope,
1191 "filters": filterList,
1192 "maxSize": maxSize,
1193 },
1194 debuggerId,
1195 )
1196
1197 def remoteClientVariable(
1198 self, debuggerId, scope, filterList, var, framenr=0, maxSize=0
1199 ):
1200 """
1201 Public method to request the variables of the debugged program.
1202
1203 @param debuggerId ID of the debugger backend
1204 @type str
1205 @param scope the scope of the variables (0 = local, 1 = global)
1206 @type int
1207 @param filterList list of variable types to filter out
1208 @type list of str
1209 @param var list encoded name of variable to retrieve
1210 @type list of str
1211 @param framenr framenumber of the variables to retrieve
1212 @type int
1213 @param maxSize maximum size the formatted value of a variable will
1214 be shown. If it is bigger than that, a 'too big' indication will
1215 be given (@@TOO_BIG_TO_SHOW@@).
1216 @type int
1217 """
1218 self.__sendJsonCommand(
1219 "RequestVariable",
1220 {
1221 "variable": var,
1222 "frameNumber": framenr,
1223 "scope": scope,
1224 "filters": filterList,
1225 "maxSize": maxSize,
1226 },
1227 debuggerId,
1228 )
1229
1230 def remoteClientDisassembly(self, debuggerId):
1231 """
1232 Public method to ask the client for the latest traceback disassembly.
1233
1234 @param debuggerId ID of the debugger backend
1235 @type str
1236 """
1237 self.__sendJsonCommand("RequestDisassembly", {}, debuggerId)
1238
1239 def remoteClientSetFilter(self, debuggerId, scope, filterStr):
1240 """
1241 Public method to set a variables filter list.
1242
1243 @param debuggerId ID of the debugger backend
1244 @type str
1245 @param scope the scope of the variables (0 = local, 1 = global)
1246 @type int
1247 @param filterStr regexp string for variable names to filter out
1248 @type str
1249 """
1250 self.__sendJsonCommand(
1251 "RequestSetFilter",
1252 {
1253 "scope": scope,
1254 "filter": filterStr,
1255 },
1256 debuggerId,
1257 )
1258
1259 def setCallTraceEnabled(self, debuggerId, on):
1260 """
1261 Public method to set the call trace state.
1262
1263 @param debuggerId ID of the debugger backend
1264 @type str
1265 @param on flag indicating to enable the call trace function
1266 @type bool
1267 """
1268 self.__sendJsonCommand(
1269 "RequestCallTrace",
1270 {
1271 "enable": on,
1272 },
1273 debuggerId,
1274 )
1275
1276 def remoteNoDebugList(self, debuggerId, noDebugList):
1277 """
1278 Public method to set a list of programs not to be debugged.
1279
1280 The programs given in the list will not be run under the control
1281 of the multi process debugger.
1282
1283 @param debuggerId ID of the debugger backend
1284 @type str
1285 @param noDebugList list of Python programs not to be debugged
1286 @type list of str
1287 """
1288 self.__sendJsonCommand(
1289 "RequestSetNoDebugList",
1290 {
1291 "noDebug": noDebugList,
1292 },
1293 debuggerId,
1294 )
1295
1296 def remoteBanner(self):
1297 """
1298 Public slot to get the banner info of the remote client.
1299 """
1300 self.__sendJsonCommand("RequestBanner", {})
1301
1302 def remoteCapabilities(self, debuggerId):
1303 """
1304 Public slot to get the debug clients capabilities.
1305
1306 @param debuggerId ID of the debugger backend
1307 @type str
1308 """
1309 self.__sendJsonCommand("RequestCapabilities", {}, debuggerId)
1310
1311 def remoteCompletion(self, debuggerId, text):
1312 """
1313 Public slot to get the a list of possible commandline completions
1314 from the remote client.
1315
1316 @param debuggerId ID of the debugger backend
1317 @type str
1318 @param text the text to be completed
1319 @type str
1320 """
1321 self.__sendJsonCommand(
1322 "RequestCompletion",
1323 {
1324 "text": text,
1325 },
1326 debuggerId,
1327 )
1328
1329 def __parseClientLine(self, sock):
1330 """
1331 Private method to handle data from the client.
1332
1333 @param sock reference to the socket to read data from
1334 @type QTcpSocket
1335 """
1336 while sock and sock.canReadLine():
1337 qs = sock.readLine()
1338 line = bytes(qs).decode(encoding=Preferences.getSystem("StringEncoding"))
1339
1340 logging.debug("<Debug-Server> %s", line)
1341 ##print("Server: ", line) ## debug # __IGNORE_WARNING_M891__
1342
1343 self.__handleJsonCommand(line, sock)
1344
1345 def __handleJsonCommand(self, jsonStr, sock):
1346 """
1347 Private method to handle a command or response serialized as a
1348 JSON string.
1349
1350 @param jsonStr string containing the command or response received
1351 from the debug backend
1352 @type str
1353 @param sock reference to the socket the data was received from
1354 @type QTcpSocket
1355 """
1356 import json
1357
1358 try:
1359 commandDict = json.loads(jsonStr.strip())
1360 except (TypeError, ValueError) as err:
1361 EricMessageBox.critical(
1362 None,
1363 self.tr("Debug Protocol Error"),
1364 self.tr(
1365 """<p>The response received from the debugger"""
1366 """ backend could not be decoded. Please report"""
1367 """ this issue with the received data to the"""
1368 """ eric bugs email address.</p>"""
1369 """<p>Error: {0}</p>"""
1370 """<p>Data:<br/>{1}</p>"""
1371 ).format(str(err), Utilities.html_encode(jsonStr.strip())),
1372 EricMessageBox.Ok,
1373 )
1374 return
1375
1376 method = commandDict["method"]
1377 params = commandDict["params"]
1378
1379 if method == "DebuggerId":
1380 self.__assignDebuggerId(sock, params["debuggerId"])
1381
1382 elif method == "ClientOutput":
1383 self.debugServer.signalClientOutput(params["text"], params["debuggerId"])
1384
1385 elif method in ["ResponseLine", "ResponseStack"]:
1386 # Check if obsolete thread was clicked
1387 if params["stack"] == []:
1388 # Request updated list
1389 self.remoteThreadList(params["debuggerId"])
1390 return
1391 for s in params["stack"]:
1392 s[0] = self.translate(s[0], True)
1393 cf = params["stack"][0]
1394 if self.__autoContinue and params["debuggerId"] not in self.__autoContinued:
1395 self.__autoContinued.append(params["debuggerId"])
1396 QTimer.singleShot(0, lambda: self.remoteContinue(params["debuggerId"]))
1397 else:
1398 self.debugServer.signalClientLine(
1399 cf[0],
1400 int(cf[1]),
1401 params["debuggerId"],
1402 method == "ResponseStack",
1403 threadName=params["threadName"],
1404 )
1405 self.debugServer.signalClientStack(
1406 params["stack"],
1407 params["debuggerId"],
1408 threadName=params["threadName"],
1409 )
1410
1411 elif method == "CallTrace":
1412 isCall = params["event"].lower() == "c"
1413 fromInfo = params["from"]
1414 toInfo = params["to"]
1415 self.debugServer.signalClientCallTrace(
1416 isCall,
1417 fromInfo["filename"],
1418 str(fromInfo["linenumber"]),
1419 fromInfo["codename"],
1420 toInfo["filename"],
1421 str(toInfo["linenumber"]),
1422 toInfo["codename"],
1423 params["debuggerId"],
1424 )
1425
1426 elif method == "ResponseVariables":
1427 self.debugServer.signalClientVariables(
1428 params["scope"], params["variables"], params["debuggerId"]
1429 )
1430
1431 elif method == "ResponseVariable":
1432 self.debugServer.signalClientVariable(
1433 params["scope"],
1434 [params["variable"]] + params["variables"],
1435 params["debuggerId"],
1436 )
1437
1438 elif method == "ResponseThreadList":
1439 self.debugServer.signalClientThreadList(
1440 params["currentID"], params["threadList"], params["debuggerId"]
1441 )
1442
1443 elif method == "ResponseThreadSet":
1444 self.debugServer.signalClientThreadSet(params["debuggerId"])
1445
1446 elif method == "ResponseCapabilities":
1447 self.clientCapabilities = params["capabilities"]
1448 if params["debuggerId"] == self.__master:
1449 # signal only for the master connection
1450 self.debugServer.signalClientCapabilities(
1451 params["capabilities"],
1452 params["clientType"],
1453 self.__startedVenv,
1454 )
1455
1456 elif method == "ResponseBanner":
1457 if params["debuggerId"] == self.__master:
1458 # signal only for the master connection
1459 self.debugServer.signalClientBanner(
1460 params["version"],
1461 params["platform"],
1462 self.__startedVenv,
1463 )
1464
1465 elif method == "ResponseOK":
1466 self.debugServer.signalClientStatement(False, params["debuggerId"])
1467
1468 elif method == "ResponseContinue":
1469 self.debugServer.signalClientStatement(True, params["debuggerId"])
1470
1471 elif method == "RequestRaw":
1472 self.debugServer.signalClientRawInput(
1473 params["prompt"], params["echo"], params["debuggerId"]
1474 )
1475
1476 elif method == "ResponseBPConditionError":
1477 fn = self.translate(params["filename"], True)
1478 self.debugServer.signalClientBreakConditionError(
1479 fn, params["line"], params["debuggerId"]
1480 )
1481
1482 elif method == "ResponseClearBreakpoint":
1483 fn = self.translate(params["filename"], True)
1484 self.debugServer.signalClientClearBreak(
1485 fn, params["line"], params["debuggerId"]
1486 )
1487
1488 elif method == "ResponseWatchConditionError":
1489 self.debugServer.signalClientWatchConditionError(
1490 params["condition"], params["debuggerId"]
1491 )
1492
1493 elif method == "ResponseClearWatch":
1494 self.debugServer.signalClientClearWatch(
1495 params["condition"], params["debuggerId"]
1496 )
1497
1498 elif method == "ResponseDisassembly":
1499 self.debugServer.signalClientDisassembly(
1500 params["disassembly"], params["debuggerId"]
1501 )
1502
1503 elif method == "ResponseException":
1504 exctype = params["type"]
1505 excmessage = params["message"]
1506 stack = params["stack"]
1507 if stack:
1508 for stackEntry in stack:
1509 stackEntry[0] = self.translate(stackEntry[0], True)
1510 if stack[0] and stack[0][0] == "<string>":
1511 for stackEntry in stack:
1512 if stackEntry[0] == "<string>":
1513 stackEntry[0] = self.__scriptName
1514 else:
1515 break
1516
1517 self.debugServer.signalClientException(
1518 exctype, excmessage, stack, params["debuggerId"], params["threadName"]
1519 )
1520
1521 elif method == "ResponseSyntax":
1522 self.debugServer.signalClientSyntaxError(
1523 params["message"],
1524 self.translate(params["filename"], True),
1525 params["linenumber"],
1526 params["characternumber"],
1527 params["debuggerId"],
1528 params["threadName"],
1529 )
1530
1531 elif method == "ResponseSignal":
1532 self.debugServer.signalClientSignal(
1533 params["message"],
1534 self.translate(params["filename"], True),
1535 params["linenumber"],
1536 params["function"],
1537 params["arguments"],
1538 params["debuggerId"],
1539 )
1540
1541 elif method == "ResponseExit":
1542 self.__scriptName = ""
1543 self.debugServer.signalClientExit(
1544 params["program"],
1545 params["status"],
1546 params["message"],
1547 params["debuggerId"],
1548 )
1549 if params["debuggerId"] == self.__master:
1550 self.debugServer.signalMainClientExit()
1551
1552 elif method == "PassiveStartup":
1553 self.debugServer.passiveStartUp(
1554 self.translate(params["filename"], True),
1555 params["exceptions"],
1556 params["debuggerId"],
1557 )
1558
1559 elif method == "ResponseCompletion":
1560 self.debugServer.signalClientCompletionList(
1561 params["completions"], params["text"], params["debuggerId"]
1562 )
1563
1564 def __sendJsonCommand(self, command, params, debuggerId="", sock=None):
1565 """
1566 Private method to send a single command to the client.
1567
1568 @param command command name to be sent
1569 @type str
1570 @param params dictionary of named parameters for the command
1571 @type dict
1572 @param debuggerId id of the debug client to send the command to
1573 @type str
1574 @param sock reference to the socket object to be used (only used if
1575 debuggerId is not given)
1576 @type QTcpSocket
1577 """
1578 import json
1579
1580 commandDict = {
1581 "jsonrpc": "2.0",
1582 "method": command,
1583 "params": params,
1584 }
1585 cmd = json.dumps(commandDict) + "\n"
1586
1587 if debuggerId and debuggerId in self.__connections:
1588 sock = self.__connections[debuggerId]
1589 elif sock is None and self.__master is not None:
1590 sock = self.__connections[self.__master]
1591 if sock is not None:
1592 self.__writeJsonCommandToSocket(cmd, sock)
1593 else:
1594 self.queue.append(cmd)
1595
1596 def __writeJsonCommandToSocket(self, cmd, sock):
1597 """
1598 Private method to write a JSON command to the socket.
1599
1600 @param cmd JSON command to be sent
1601 @type str
1602 @param sock reference to the socket to write to
1603 @type QTcpSocket
1604 """
1605 data = cmd.encode("utf8", "backslashreplace")
1606 length = "{0:09d}".format(len(data))
1607 sock.write(length.encode() + data)
1608 sock.flush()
1609
1610
1611 def createDebuggerInterfacePython3(debugServer, passive):
1612 """
1613 Module function to create a debugger interface instance.
1614
1615
1616 @param debugServer reference to the debug server
1617 @type DebugServer
1618 @param passive flag indicating passive connection mode
1619 @type bool
1620 @return instantiated debugger interface
1621 @rtype DebuggerInterfacePython
1622 """
1623 return DebuggerInterfacePython(debugServer, passive)
1624
1625
1626 def getRegistryData():
1627 """
1628 Module function to get characterizing data for the supported debugger
1629 interfaces.
1630
1631 @return list of tuples containing the client type, the client capabilities,
1632 the client file type associations and a reference to the creation
1633 function
1634 @rtype list of tuple of (str, int, list of str, function)
1635 """
1636 py3Exts = []
1637 for ext in Preferences.getDebugger("Python3Extensions").split():
1638 if ext.startswith("."):
1639 py3Exts.append(ext)
1640 else:
1641 py3Exts.append(".{0}".format(ext))
1642
1643 registryData = []
1644 if py3Exts:
1645 registryData.append(
1646 (
1647 "Python3",
1648 ClientDefaultCapabilities,
1649 py3Exts,
1650 createDebuggerInterfacePython3,
1651 )
1652 )
1653
1654 return registryData

eric ide

mercurial