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