Debugger/DebuggerInterfacePython.py

branch
jsonrpc
changeset 5132
a094eee9f862
parent 5131
889ed5ff7a68
child 5133
b7fe69c6cb1c
equal deleted inserted replaced
5131:889ed5ff7a68 5132:a094eee9f862
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Python debugger interface for the debug server.
8 """
9
10 from __future__ import unicode_literals
11
12 import sys
13 import os
14 import re
15
16 from PyQt5.QtCore import QObject, QTextCodec, QProcess, QProcessEnvironment, \
17 QTimer
18 from PyQt5.QtWidgets import QInputDialog
19
20 from E5Gui.E5Application import e5App
21 from E5Gui import E5MessageBox
22
23 from . import DebugProtocol
24 from . import DebugClientCapabilities
25
26 import Preferences
27 import Utilities
28
29 from eric6config import getConfig
30
31
32 ClientDefaultCapabilities = DebugClientCapabilities.HasAll
33
34
35 class DebuggerInterfacePython(QObject):
36 """
37 Class implementing the Python debugger interface for the debug server.
38 """
39 def __init__(self, debugServer, passive):
40 """
41 Constructor
42
43 @param debugServer reference to the debug server (DebugServer)
44 @param passive flag indicating passive connection mode (boolean)
45 """
46 super(DebuggerInterfacePython, self).__init__()
47
48 self.__isNetworked = True
49 self.__autoContinue = False
50
51 self.debugServer = debugServer
52 self.passive = passive
53 self.process = None
54
55 self.qsock = None
56 self.queue = []
57
58 # set default values for capabilities of clients
59 self.clientCapabilities = ClientDefaultCapabilities
60
61 # set translation function
62 self.translate = self.__identityTranslation
63
64 self.codec = QTextCodec.codecForName(
65 Preferences.getSystem("StringEncoding"))
66
67 self.__unicodeRe = re.compile(r"""\bu(["'])""")
68
69 if passive:
70 # set translation function
71 if Preferences.getDebugger("PathTranslation"):
72 self.translateRemote = \
73 Preferences.getDebugger("PathTranslationRemote")
74 self.translateLocal = \
75 Preferences.getDebugger("PathTranslationLocal")
76 self.translate = self.__remoteTranslation
77 else:
78 self.translate = self.__identityTranslation
79
80 # attribute to remember the name of the executed script
81 self.__scriptName = ""
82
83 def __identityTranslation(self, fn, remote2local=True):
84 """
85 Private method to perform the identity path translation.
86
87 @param fn filename to be translated (string)
88 @param remote2local flag indicating the direction of translation
89 (False = local to remote, True = remote to local [default])
90 @return translated filename (string)
91 """
92 return fn
93
94 def __remoteTranslation(self, fn, remote2local=True):
95 """
96 Private method to perform the path translation.
97
98 @param fn filename to be translated (string)
99 @param remote2local flag indicating the direction of translation
100 (False = local to remote, True = remote to local [default])
101 @return translated filename (string)
102 """
103 if remote2local:
104 return fn.replace(self.translateRemote, self.translateLocal)
105 else:
106 return fn.replace(self.translateLocal, self.translateRemote)
107
108 def __startProcess(self, program, arguments, environment=None):
109 """
110 Private method to start the debugger client process.
111
112 @param program name of the executable to start (string)
113 @param arguments arguments to be passed to the program (list of string)
114 @param environment dictionary of environment settings to pass
115 (dict of string)
116 @return the process object (QProcess) or None
117 """
118 proc = QProcess()
119 if environment is not None:
120 env = QProcessEnvironment()
121 for key, value in list(environment.items()):
122 env.insert(key, value)
123 proc.setProcessEnvironment(env)
124 args = []
125 for arg in arguments:
126 args.append(arg)
127 proc.start(program, args)
128 if not proc.waitForStarted(10000):
129 proc = None
130
131 return proc
132
133 def startRemote(self, port, runInConsole):
134 """
135 Public method to start a remote Python interpreter.
136
137 @param port portnumber the debug server is listening on (integer)
138 @param runInConsole flag indicating to start the debugger in a
139 console window (boolean)
140 @return client process object (QProcess), a flag to indicate
141 a network connection (boolean) and the name of the interpreter
142 in case of a local execution (string)
143 """
144 interpreter = Preferences.getDebugger("PythonInterpreter")
145 if interpreter == "":
146 E5MessageBox.critical(
147 None,
148 self.tr("Start Debugger"),
149 self.tr(
150 """<p>No Python2 interpreter configured.</p>"""))
151 return None, False, ""
152
153 debugClientType = Preferences.getDebugger("DebugClientType")
154 if debugClientType == "standard":
155 debugClient = os.path.join(getConfig('ericDir'),
156 "DebugClients", "Python",
157 "DebugClient.py")
158 elif debugClientType == "threaded":
159 debugClient = os.path.join(getConfig('ericDir'),
160 "DebugClients", "Python",
161 "DebugClientThreads.py")
162 else:
163 debugClient = Preferences.getDebugger("DebugClient")
164 if debugClient == "":
165 debugClient = os.path.join(sys.path[0],
166 "DebugClients", "Python",
167 "DebugClient.py")
168
169 redirect = str(Preferences.getDebugger("PythonRedirect"))
170 noencoding = Preferences.getDebugger("PythonNoEncoding") and \
171 '--no-encoding' or ''
172
173 if Preferences.getDebugger("RemoteDbgEnabled"):
174 ipaddr = self.debugServer.getHostAddress(False)
175 rexec = Preferences.getDebugger("RemoteExecution")
176 rhost = Preferences.getDebugger("RemoteHost")
177 if rhost == "":
178 rhost = "localhost"
179 if rexec:
180 args = Utilities.parseOptionString(rexec) + \
181 [rhost, interpreter, debugClient,
182 noencoding, str(port), redirect, ipaddr]
183 args[0] = Utilities.getExecutablePath(args[0])
184 process = self.__startProcess(args[0], args[1:])
185 if process is None:
186 E5MessageBox.critical(
187 None,
188 self.tr("Start Debugger"),
189 self.tr(
190 """<p>The debugger backend could not be"""
191 """ started.</p>"""))
192
193 # set translation function
194 if Preferences.getDebugger("PathTranslation"):
195 self.translateRemote = \
196 Preferences.getDebugger("PathTranslationRemote")
197 self.translateLocal = \
198 Preferences.getDebugger("PathTranslationLocal")
199 self.translate = self.__remoteTranslation
200 else:
201 self.translate = self.__identityTranslation
202 return process, self.__isNetworked, ""
203
204 # set translation function
205 self.translate = self.__identityTranslation
206
207 # setup the environment for the debugger
208 if Preferences.getDebugger("DebugEnvironmentReplace"):
209 clientEnv = {}
210 else:
211 clientEnv = os.environ.copy()
212 envlist = Utilities.parseEnvironmentString(
213 Preferences.getDebugger("DebugEnvironment"))
214 for el in envlist:
215 try:
216 key, value = el.split('=', 1)
217 if value.startswith('"') or value.startswith("'"):
218 value = value[1:-1]
219 clientEnv[str(key)] = str(value)
220 except ValueError:
221 pass
222
223 ipaddr = self.debugServer.getHostAddress(True)
224 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"):
225 ccmd = Preferences.getDebugger("ConsoleDbgCommand")
226 if ccmd:
227 args = Utilities.parseOptionString(ccmd) + \
228 [interpreter, os.path.abspath(debugClient),
229 noencoding, str(port), '0', ipaddr]
230 args[0] = Utilities.getExecutablePath(args[0])
231 process = self.__startProcess(args[0], args[1:], clientEnv)
232 if process is None:
233 E5MessageBox.critical(
234 None,
235 self.tr("Start Debugger"),
236 self.tr(
237 """<p>The debugger backend could not be"""
238 """ started.</p>"""))
239 return process, self.__isNetworked, interpreter
240
241 process = self.__startProcess(
242 interpreter,
243 [debugClient, noencoding, str(port), redirect, ipaddr],
244 clientEnv)
245 if process is None:
246 E5MessageBox.critical(
247 None,
248 self.tr("Start Debugger"),
249 self.tr(
250 """<p>The debugger backend could not be started.</p>"""))
251 return process, self.__isNetworked, interpreter
252
253 def startRemoteForProject(self, port, runInConsole):
254 """
255 Public method to start a remote Python interpreter for a project.
256
257 @param port portnumber the debug server is listening on (integer)
258 @param runInConsole flag indicating to start the debugger in a
259 console window (boolean)
260 @return client process object (QProcess), a flag to indicate
261 a network connection (boolean) and the name of the interpreter
262 in case of a local execution (string)
263 """
264 project = e5App().getObject("Project")
265 if not project.isDebugPropertiesLoaded():
266 return None, self.__isNetworked, ""
267
268 # start debugger with project specific settings
269 interpreter = project.getDebugProperty("INTERPRETER")
270 debugClient = project.getDebugProperty("DEBUGCLIENT")
271
272 redirect = str(project.getDebugProperty("REDIRECT"))
273 noencoding = \
274 project.getDebugProperty("NOENCODING") and '--no-encoding' or ''
275
276 if project.getDebugProperty("REMOTEDEBUGGER"):
277 ipaddr = self.debugServer.getHostAddress(False)
278 rexec = project.getDebugProperty("REMOTECOMMAND")
279 rhost = project.getDebugProperty("REMOTEHOST")
280 if rhost == "":
281 rhost = "localhost"
282 if rexec:
283 args = Utilities.parseOptionString(rexec) + \
284 [rhost, interpreter, os.path.abspath(debugClient),
285 noencoding, str(port), redirect, ipaddr]
286 args[0] = Utilities.getExecutablePath(args[0])
287 process = self.__startProcess(args[0], args[1:])
288 if process is None:
289 E5MessageBox.critical(
290 None,
291 self.tr("Start Debugger"),
292 self.tr(
293 """<p>The debugger backend could not be"""
294 """ started.</p>"""))
295 # set translation function
296 if project.getDebugProperty("PATHTRANSLATION"):
297 self.translateRemote = \
298 project.getDebugProperty("REMOTEPATH")
299 self.translateLocal = \
300 project.getDebugProperty("LOCALPATH")
301 self.translate = self.__remoteTranslation
302 else:
303 self.translate = self.__identityTranslation
304 return process, self.__isNetworked, ""
305
306 # set translation function
307 self.translate = self.__identityTranslation
308
309 # setup the environment for the debugger
310 if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
311 clientEnv = {}
312 else:
313 clientEnv = os.environ.copy()
314 envlist = Utilities.parseEnvironmentString(
315 project.getDebugProperty("ENVIRONMENTSTRING"))
316 for el in envlist:
317 try:
318 key, value = el.split('=', 1)
319 if value.startswith('"') or value.startswith("'"):
320 value = value[1:-1]
321 clientEnv[str(key)] = str(value)
322 except ValueError:
323 pass
324
325 ipaddr = self.debugServer.getHostAddress(True)
326 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"):
327 ccmd = project.getDebugProperty("CONSOLECOMMAND") or \
328 Preferences.getDebugger("ConsoleDbgCommand")
329 if ccmd:
330 args = Utilities.parseOptionString(ccmd) + \
331 [interpreter, os.path.abspath(debugClient),
332 noencoding, str(port), '0', ipaddr]
333 args[0] = Utilities.getExecutablePath(args[0])
334 process = self.__startProcess(args[0], args[1:], clientEnv)
335 if process is None:
336 E5MessageBox.critical(
337 None,
338 self.tr("Start Debugger"),
339 self.tr(
340 """<p>The debugger backend could not be"""
341 """ started.</p>"""))
342 return process, self.__isNetworked, interpreter
343
344 process = self.__startProcess(
345 interpreter,
346 [debugClient, noencoding, str(port), redirect, ipaddr],
347 clientEnv)
348 if process is None:
349 E5MessageBox.critical(
350 None,
351 self.tr("Start Debugger"),
352 self.tr(
353 """<p>The debugger backend could not be started.</p>"""))
354 return process, self.__isNetworked, interpreter
355
356 def getClientCapabilities(self):
357 """
358 Public method to retrieve the debug clients capabilities.
359
360 @return debug client capabilities (integer)
361 """
362 return self.clientCapabilities
363
364 def newConnection(self, sock):
365 """
366 Public slot to handle a new connection.
367
368 @param sock reference to the socket object (QTcpSocket)
369 @return flag indicating success (boolean)
370 """
371 # If we already have a connection, refuse this one. It will be closed
372 # automatically.
373 if self.qsock is not None:
374 return False
375
376 sock.disconnected.connect(self.debugServer.startClient)
377 sock.readyRead.connect(self.__parseClientLine)
378
379 self.qsock = sock
380
381 # Get the remote clients capabilities
382 self.remoteCapabilities()
383 return True
384
385 def flush(self):
386 """
387 Public slot to flush the queue.
388 """
389 # Send commands that were waiting for the connection.
390 for cmd in self.queue:
391 self.qsock.write(cmd.encode('utf8'))
392
393 self.queue = []
394
395 def shutdown(self):
396 """
397 Public method to cleanly shut down.
398
399 It closes our socket and shuts down
400 the debug client. (Needed on Win OS)
401 """
402 if self.qsock is None:
403 return
404
405 # do not want any slots called during shutdown
406 self.qsock.disconnected.disconnect(self.debugServer.startClient)
407 self.qsock.readyRead.disconnect(self.__parseClientLine)
408
409 # close down socket, and shut down client as well.
410 self.__sendCommand('{0}\n'.format(DebugProtocol.RequestShutdown))
411 self.qsock.flush()
412
413 self.qsock.close()
414
415 # reinitialize
416 self.qsock = None
417 self.queue = []
418
419 def isConnected(self):
420 """
421 Public method to test, if a debug client has connected.
422
423 @return flag indicating the connection status (boolean)
424 """
425 return self.qsock is not None
426
427 def remoteEnvironment(self, env):
428 """
429 Public method to set the environment for a program to debug, run, ...
430
431 @param env environment settings (dictionary)
432 """
433 self.__sendCommand('{0}{1}\n'.format(
434 DebugProtocol.RequestEnv, str(env)))
435
436 def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
437 autoContinue=True, autoFork=False, forkChild=False):
438 """
439 Public method to load a new program to debug.
440
441 @param fn the filename to debug (string)
442 @param argv the commandline arguments to pass to the program (string)
443 @param wd the working directory for the program (string)
444 @keyparam traceInterpreter flag indicating if the interpreter library
445 should be traced as well (boolean)
446 @keyparam autoContinue flag indicating, that the debugger should not
447 stop at the first executable line (boolean)
448 @keyparam autoFork flag indicating the automatic fork mode (boolean)
449 @keyparam forkChild flag indicating to debug the child after forking
450 (boolean)
451 """
452 self.__autoContinue = autoContinue
453 self.__scriptName = os.path.abspath(fn)
454
455 wd = self.translate(wd, False)
456 fn = self.translate(os.path.abspath(fn), False)
457 self.__sendCommand('{0}{1}\n'.format(
458 DebugProtocol.RequestForkMode, repr((autoFork, forkChild))))
459 self.__sendCommand('{0}{1}|{2}|{3}|{4:d}\n'.format(
460 DebugProtocol.RequestLoad, wd, fn,
461 str(Utilities.parseOptionString(argv)),
462 traceInterpreter))
463
464 def remoteRun(self, fn, argv, wd, autoFork=False, forkChild=False):
465 """
466 Public method to load a new program to run.
467
468 @param fn the filename to run (string)
469 @param argv the commandline arguments to pass to the program (string)
470 @param wd the working directory for the program (string)
471 @keyparam autoFork flag indicating the automatic fork mode (boolean)
472 @keyparam forkChild flag indicating to debug the child after forking
473 (boolean)
474 """
475 self.__scriptName = os.path.abspath(fn)
476
477 wd = self.translate(wd, False)
478 fn = self.translate(os.path.abspath(fn), False)
479 self.__sendCommand('{0}{1}\n'.format(
480 DebugProtocol.RequestForkMode, repr((autoFork, forkChild))))
481 self.__sendCommand('{0}{1}|{2}|{3}\n'.format(
482 DebugProtocol.RequestRun, wd, fn,
483 str(Utilities.parseOptionString(argv))))
484
485 def remoteCoverage(self, fn, argv, wd, erase=False):
486 """
487 Public method to load a new program to collect coverage data.
488
489 @param fn the filename to run (string)
490 @param argv the commandline arguments to pass to the program (string)
491 @param wd the working directory for the program (string)
492 @keyparam erase flag indicating that coverage info should be
493 cleared first (boolean)
494 """
495 self.__scriptName = os.path.abspath(fn)
496
497 wd = self.translate(wd, False)
498 fn = self.translate(os.path.abspath(fn), False)
499 self.__sendCommand('{0}{1}@@{2}@@{3}@@{4:d}\n'.format(
500 DebugProtocol.RequestCoverage, wd, fn,
501 str(Utilities.parseOptionString(argv)), erase))
502
503 def remoteProfile(self, fn, argv, wd, erase=False):
504 """
505 Public method to load a new program to collect profiling data.
506
507 @param fn the filename to run (string)
508 @param argv the commandline arguments to pass to the program (string)
509 @param wd the working directory for the program (string)
510 @keyparam erase flag indicating that timing info should be cleared
511 first (boolean)
512 """
513 self.__scriptName = os.path.abspath(fn)
514
515 wd = self.translate(wd, False)
516 fn = self.translate(os.path.abspath(fn), False)
517 self.__sendCommand('{0}{1}|{2}|{3}|{4:d}\n'.format(
518 DebugProtocol.RequestProfile, wd, fn,
519 str(Utilities.parseOptionString(argv)), erase))
520
521 def remoteStatement(self, stmt):
522 """
523 Public method to execute a Python statement.
524
525 @param stmt the Python statement to execute (string). It
526 should not have a trailing newline.
527 """
528 self.__sendCommand('{0}\n'.format(stmt))
529 self.__sendCommand(DebugProtocol.RequestOK + '\n')
530
531 def remoteStep(self):
532 """
533 Public method to single step the debugged program.
534 """
535 self.__sendCommand(DebugProtocol.RequestStep + '\n')
536
537 def remoteStepOver(self):
538 """
539 Public method to step over the debugged program.
540 """
541 self.__sendCommand(DebugProtocol.RequestStepOver + '\n')
542
543 def remoteStepOut(self):
544 """
545 Public method to step out the debugged program.
546 """
547 self.__sendCommand(DebugProtocol.RequestStepOut + '\n')
548
549 def remoteStepQuit(self):
550 """
551 Public method to stop the debugged program.
552 """
553 self.__sendCommand(DebugProtocol.RequestStepQuit + '\n')
554
555 def remoteContinue(self, special=False):
556 """
557 Public method to continue the debugged program.
558
559 @param special flag indicating a special continue operation (boolean)
560 """
561 self.__sendCommand('{0}{1:d}\n'.format(
562 DebugProtocol.RequestContinue, special))
563
564 def remoteBreakpoint(self, fn, line, set, cond=None, temp=False):
565 """
566 Public method to set or clear a breakpoint.
567
568 @param fn filename the breakpoint belongs to (string)
569 @param line linenumber of the breakpoint (int)
570 @param set flag indicating setting or resetting a breakpoint (boolean)
571 @param cond condition of the breakpoint (string)
572 @param temp flag indicating a temporary breakpoint (boolean)
573 """
574 fn = self.translate(fn, False)
575 self.__sendCommand('{0}{1}@@{2:d}@@{3:d}@@{4:d}@@{5}\n'.format(
576 DebugProtocol.RequestBreak, fn, line, temp, set,
577 cond))
578
579 def remoteBreakpointEnable(self, fn, line, enable):
580 """
581 Public method to enable or disable a breakpoint.
582
583 @param fn filename the breakpoint belongs to (string)
584 @param line linenumber of the breakpoint (int)
585 @param enable flag indicating enabling or disabling a breakpoint
586 (boolean)
587 """
588 fn = self.translate(fn, False)
589 self.__sendCommand('{0}{1},{2:d},{3:d}\n'.format(
590 DebugProtocol.RequestBreakEnable, fn, line, enable))
591
592 def remoteBreakpointIgnore(self, fn, line, count):
593 """
594 Public method to ignore a breakpoint the next couple of occurrences.
595
596 @param fn filename the breakpoint belongs to (string)
597 @param line linenumber of the breakpoint (int)
598 @param count number of occurrences to ignore (int)
599 """
600 fn = self.translate(fn, False)
601 self.__sendCommand('{0}{1},{2:d},{3:d}\n'.format(
602 DebugProtocol.RequestBreakIgnore, fn, line, count))
603
604 def remoteWatchpoint(self, cond, set, temp=False):
605 """
606 Public method to set or clear a watch expression.
607
608 @param cond expression of the watch expression (string)
609 @param set flag indicating setting or resetting a watch expression
610 (boolean)
611 @param temp flag indicating a temporary watch expression (boolean)
612 """
613 # cond is combination of cond and special (s. watch expression viewer)
614 self.__sendCommand('{0}{1}@@{2:d}@@{3:d}\n'.format(
615 DebugProtocol.RequestWatch, cond, temp, set))
616
617 def remoteWatchpointEnable(self, cond, enable):
618 """
619 Public method to enable or disable a watch expression.
620
621 @param cond expression of the watch expression (string)
622 @param enable flag indicating enabling or disabling a watch expression
623 (boolean)
624 """
625 # cond is combination of cond and special (s. watch expression viewer)
626 self.__sendCommand('{0}{1},{2:d}\n'.format(
627 DebugProtocol.RequestWatchEnable, cond, enable))
628
629 def remoteWatchpointIgnore(self, cond, count):
630 """
631 Public method to ignore a watch expression the next couple of
632 occurrences.
633
634 @param cond expression of the watch expression (string)
635 @param count number of occurrences to ignore (int)
636 """
637 # cond is combination of cond and special (s. watch expression viewer)
638 self.__sendCommand('{0}{1},{2:d}\n'.format(
639 DebugProtocol.RequestWatchIgnore, cond, count))
640
641 def remoteRawInput(self, s):
642 """
643 Public method to send the raw input to the debugged program.
644
645 @param s the raw input (string)
646 """
647 self.__sendCommand(s + '\n')
648
649 def remoteThreadList(self):
650 """
651 Public method to request the list of threads from the client.
652 """
653 self.__sendCommand('{0}\n'.format(DebugProtocol.RequestThreadList))
654
655 def remoteSetThread(self, tid):
656 """
657 Public method to request to set the given thread as current thread.
658
659 @param tid id of the thread (integer)
660 """
661 self.__sendCommand('{0}{1:d}\n'.format(
662 DebugProtocol.RequestThreadSet, tid))
663
664 def remoteClientVariables(self, scope, filter, framenr=0):
665 """
666 Public method to request the variables of the debugged program.
667
668 @param scope the scope of the variables (0 = local, 1 = global)
669 @param filter list of variable types to filter out (list of int)
670 @param framenr framenumber of the variables to retrieve (int)
671 """
672 self.__sendCommand('{0}{1:d}, {2:d}, {3}\n'.format(
673 DebugProtocol.RequestVariables, framenr, scope, str(filter)))
674
675 def remoteClientVariable(self, scope, filter, var, framenr=0):
676 """
677 Public method to request the variables of the debugged program.
678
679 @param scope the scope of the variables (0 = local, 1 = global)
680 @param filter list of variable types to filter out (list of int)
681 @param var list encoded name of variable to retrieve (string)
682 @param framenr framenumber of the variables to retrieve (int)
683 """
684 self.__sendCommand('{0}{1}, {2:d}, {3:d}, {4}\n'.format(
685 DebugProtocol.RequestVariable, str(var), framenr, scope,
686 str(filter)))
687
688 def remoteClientSetFilter(self, scope, filter):
689 """
690 Public method to set a variables filter list.
691
692 @param scope the scope of the variables (0 = local, 1 = global)
693 @param filter regexp string for variable names to filter out (string)
694 """
695 self.__sendCommand('{0}{1:d}, "{2}"\n'.format(
696 DebugProtocol.RequestSetFilter, scope, filter))
697
698 def setCallTraceEnabled(self, on):
699 """
700 Public method to set the call trace state.
701
702 @param on flag indicating to enable the call trace function (boolean)
703 """
704 if on:
705 cmd = "on"
706 else:
707 cmd = "off"
708 self.__sendCommand('{0}{1}\n'.format(
709 DebugProtocol.RequestCallTrace, cmd))
710
711 def remoteEval(self, arg):
712 """
713 Public method to evaluate arg in the current context of the debugged
714 program.
715
716 @param arg the arguments to evaluate (string)
717 """
718 self.__sendCommand('{0}{1}\n'.format(DebugProtocol.RequestEval, arg))
719
720 def remoteExec(self, stmt):
721 """
722 Public method to execute stmt in the current context of the debugged
723 program.
724
725 @param stmt statement to execute (string)
726 """
727 self.__sendCommand('{0}{1}\n'.format(DebugProtocol.RequestExec, stmt))
728
729 def remoteBanner(self):
730 """
731 Public slot to get the banner info of the remote client.
732 """
733 self.__sendCommand(DebugProtocol.RequestBanner + '\n')
734
735 def remoteCapabilities(self):
736 """
737 Public slot to get the debug clients capabilities.
738 """
739 self.__sendCommand(DebugProtocol.RequestCapabilities + '\n')
740
741 def remoteCompletion(self, text):
742 """
743 Public slot to get the a list of possible commandline completions
744 from the remote client.
745
746 @param text the text to be completed (string)
747 """
748 self.__sendCommand("{0}{1}\n".format(
749 DebugProtocol.RequestCompletion, text))
750
751 def remoteUTPrepare(self, fn, tn, tfn, failed, cov, covname, coverase):
752 """
753 Public method to prepare a new unittest run.
754
755 @param fn the filename to load (string)
756 @param tn the testname to load (string)
757 @param tfn the test function name to load tests from (string)
758 @param failed list of failed test, if only failed test should be run
759 (list of strings)
760 @param cov flag indicating collection of coverage data is requested
761 (boolean)
762 @param covname filename to be used to assemble the coverage caches
763 filename (string)
764 @param coverase flag indicating erasure of coverage data is requested
765 (boolean)
766 """
767 self.__scriptName = os.path.abspath(fn)
768
769 fn = self.translate(os.path.abspath(fn), False)
770 self.__sendCommand('{0}{1}|{2}|{3}|{4}|{5:d}|{6}|{7:d}\n'.format(
771 DebugProtocol.RequestUTPrepare, fn, tn, tfn, str(failed),
772 cov, covname, coverase))
773
774 def remoteUTRun(self):
775 """
776 Public method to start a unittest run.
777 """
778 self.__sendCommand('{0}\n'.format(DebugProtocol.RequestUTRun))
779
780 def remoteUTStop(self):
781 """
782 Public method to stop a unittest run.
783 """
784 self.__sendCommand('{0}\n'.format(DebugProtocol.RequestUTStop))
785
786 def __askForkTo(self):
787 """
788 Private method to ask the user which branch of a fork to follow.
789 """
790 selections = [self.tr("Parent Process"),
791 self.tr("Child process")]
792 res, ok = QInputDialog.getItem(
793 None,
794 self.tr("Client forking"),
795 self.tr("Select the fork branch to follow."),
796 selections,
797 0, False)
798 if not ok or res == selections[0]:
799 self.__sendCommand(DebugProtocol.ResponseForkTo + 'parent\n')
800 else:
801 self.__sendCommand(DebugProtocol.ResponseForkTo + 'child\n')
802
803 def __parseClientLine(self):
804 """
805 Private method to handle data from the client.
806 """
807 while self.qsock and self.qsock.canReadLine():
808 qs = self.qsock.readLine()
809 if self.codec is not None:
810 line = self.codec.toUnicode(qs)
811 else:
812 line = bytes(qs).decode()
813 if line.endswith(DebugProtocol.EOT):
814 line = line[:-len(DebugProtocol.EOT)]
815 if not line:
816 continue
817
818 ## print("Server: ", line) ##debug
819
820 eoc = line.find('<') + 1
821
822 # Deal with case where user has written directly to stdout
823 # or stderr, but not line terminated and we stepped over the
824 # write call, in that case the >line< will not be the first
825 # string read from the socket...
826 boc = line.find('>')
827 if boc > 0 and eoc > boc:
828 self.debugServer.signalClientOutput(line[:boc])
829 line = line[boc:]
830 eoc = line.find('<') + 1
831 boc = line.find('>')
832
833 if boc >= 0 and eoc > boc:
834 resp = line[boc:eoc]
835 evalArg = self.__unicodeRe.sub(r"\1", line[eoc:-1])
836
837 if resp == DebugProtocol.ResponseLine or \
838 resp == DebugProtocol.ResponseStack:
839 stack = eval(evalArg)
840 for s in stack:
841 s[0] = self.translate(s[0], True)
842 cf = stack[0]
843 if self.__autoContinue:
844 self.__autoContinue = False
845 QTimer.singleShot(0, self.remoteContinue)
846 else:
847 self.debugServer.signalClientLine(
848 cf[0], int(cf[1]),
849 resp == DebugProtocol.ResponseStack)
850 self.debugServer.signalClientStack(stack)
851 continue
852
853 if resp == DebugProtocol.CallTrace:
854 event, fromStr, toStr = line[eoc:-1].split("@@")
855 isCall = event.lower() == "c"
856 fromFile, fromLineno, fromFunc = fromStr.rsplit(":", 2)
857 toFile, toLineno, toFunc = toStr.rsplit(":", 2)
858 self.debugServer.signalClientCallTrace(isCall, fromFile,
859 fromLineno,
860 fromFunc,
861 toFile, toLineno,
862 toFunc)
863 continue
864
865 if resp == DebugProtocol.ResponseThreadList:
866 currentId, threadList = eval(evalArg)
867 self.debugServer.signalClientThreadList(
868 currentId, threadList)
869 continue
870
871 if resp == DebugProtocol.ResponseThreadSet:
872 self.debugServer.signalClientThreadSet()
873 continue
874
875 if resp == DebugProtocol.ResponseVariables:
876 vlist = eval(evalArg)
877 scope = vlist[0]
878 try:
879 variables = vlist[1:]
880 except IndexError:
881 variables = []
882 self.debugServer.signalClientVariables(scope, variables)
883 continue
884
885 if resp == DebugProtocol.ResponseVariable:
886 vlist = eval(evalArg)
887 scope = vlist[0]
888 try:
889 variables = vlist[1:]
890 except IndexError:
891 variables = []
892 self.debugServer.signalClientVariable(scope, variables)
893 continue
894
895 if resp == DebugProtocol.ResponseOK:
896 self.debugServer.signalClientStatement(False)
897 continue
898
899 if resp == DebugProtocol.ResponseContinue:
900 self.debugServer.signalClientStatement(True)
901 continue
902
903 if resp == DebugProtocol.ResponseException:
904 exc = self.translate(evalArg, True)
905 try:
906 exclist = eval(exc)
907 exctype = exclist[0]
908 excmessage = exclist[1]
909 stack = exclist[2:]
910 if stack and stack[0] and stack[0][0] == "<string>":
911 for stackEntry in stack:
912 if stackEntry[0] == "<string>":
913 stackEntry[0] = self.__scriptName
914 else:
915 break
916 except (IndexError, ValueError, SyntaxError):
917 exctype = None
918 excmessage = ''
919 stack = []
920 self.debugServer.signalClientException(
921 exctype, excmessage, stack)
922 continue
923
924 if resp == DebugProtocol.ResponseSyntax:
925 exc = self.translate(evalArg, True)
926 try:
927 message, (fn, ln, cn) = eval(exc)
928 if fn is None:
929 fn = ''
930 except (IndexError, ValueError):
931 message = None
932 fn = ''
933 ln = 0
934 cn = 0
935 if cn is None:
936 cn = 0
937 self.debugServer.signalClientSyntaxError(
938 message, fn, ln, cn)
939 continue
940
941 if resp == DebugProtocol.ResponseSignal:
942 sig = self.translate(evalArg, True)
943 message, (fn, ln, func, args) = eval(sig)
944 self.debugServer.signalClientSignal(
945 message, fn, ln, func, args)
946 continue
947
948 if resp == DebugProtocol.ResponseExit:
949 self.__scriptName = ""
950 self.debugServer.signalClientExit(evalArg)
951 continue
952
953 if resp == DebugProtocol.ResponseClearBreak:
954 fn, lineno = evalArg.split(',')
955 lineno = int(lineno)
956 fn = self.translate(fn, True)
957 self.debugServer.signalClientClearBreak(fn, lineno)
958 continue
959
960 if resp == DebugProtocol.ResponseBPConditionError:
961 fn, lineno = evalArg.split(',')
962 lineno = int(lineno)
963 fn = self.translate(fn, True)
964 self.debugServer.signalClientBreakConditionError(
965 fn, lineno)
966 continue
967
968 if resp == DebugProtocol.ResponseClearWatch:
969 self.debugServer.signalClientClearWatch(evalArg)
970 continue
971
972 if resp == DebugProtocol.ResponseWPConditionError:
973 self.debugServer.signalClientWatchConditionError(evalArg)
974 continue
975
976 if resp == DebugProtocol.ResponseRaw:
977 prompt, echo = eval(evalArg)
978 self.debugServer.signalClientRawInput(prompt, echo)
979 continue
980
981 if resp == DebugProtocol.ResponseBanner:
982 version, platform, dbgclient = eval(evalArg)
983 self.debugServer.signalClientBanner(
984 version, platform, dbgclient)
985 continue
986
987 if resp == DebugProtocol.ResponseCapabilities:
988 cap, clType = eval(evalArg)
989 self.clientCapabilities = cap
990 self.debugServer.signalClientCapabilities(cap, clType)
991 continue
992
993 if resp == DebugProtocol.ResponseCompletion:
994 clstring, text = evalArg.split('||')
995 cl = eval(clstring)
996 self.debugServer.signalClientCompletionList(cl, text)
997 continue
998
999 if resp == DebugProtocol.PassiveStartup:
1000 fn, exc = evalArg.split('|')
1001 exc = bool(exc)
1002 fn = self.translate(fn, True)
1003 self.debugServer.passiveStartUp(fn, exc)
1004 continue
1005
1006 if resp == DebugProtocol.ResponseUTPrepared:
1007 res, exc_type, exc_value = eval(evalArg)
1008 self.debugServer.clientUtPrepared(res, exc_type, exc_value)
1009 continue
1010
1011 if resp == DebugProtocol.ResponseUTStartTest:
1012 testname, doc = eval(evalArg)
1013 self.debugServer.clientUtStartTest(testname, doc)
1014 continue
1015
1016 if resp == DebugProtocol.ResponseUTStopTest:
1017 self.debugServer.clientUtStopTest()
1018 continue
1019
1020 if resp == DebugProtocol.ResponseUTTestFailed:
1021 testname, traceback, id = eval(evalArg)
1022 self.debugServer.clientUtTestFailed(
1023 testname, traceback, id)
1024 continue
1025
1026 if resp == DebugProtocol.ResponseUTTestErrored:
1027 testname, traceback, id = eval(evalArg)
1028 self.debugServer.clientUtTestErrored(
1029 testname, traceback, id)
1030 continue
1031
1032 if resp == DebugProtocol.ResponseUTTestSkipped:
1033 testname, reason, id = eval(line[eoc:-1])
1034 self.debugServer.clientUtTestSkipped(testname, reason, id)
1035 continue
1036
1037 if resp == DebugProtocol.ResponseUTTestFailedExpected:
1038 testname, traceback, id = eval(line[eoc:-1])
1039 self.debugServer.clientUtTestFailedExpected(
1040 testname, traceback, id)
1041 continue
1042
1043 if resp == DebugProtocol.ResponseUTTestSucceededUnexpected:
1044 testname, id = eval(line[eoc:-1])
1045 self.debugServer.clientUtTestSucceededUnexpected(
1046 testname, id)
1047 continue
1048
1049 if resp == DebugProtocol.ResponseUTFinished:
1050 self.debugServer.clientUtFinished()
1051 continue
1052
1053 if resp == DebugProtocol.RequestForkTo:
1054 self.__askForkTo()
1055 continue
1056
1057 self.debugServer.signalClientOutput(line)
1058
1059 def __sendCommand(self, cmd):
1060 """
1061 Private method to send a single line command to the client.
1062
1063 @param cmd command to send to the debug client (string)
1064 """
1065 if self.qsock is not None:
1066 self.qsock.write(cmd.encode('utf8'))
1067 else:
1068 self.queue.append(cmd)
1069
1070
1071 def createDebuggerInterfacePython(debugServer, passive):
1072 """
1073 Module function to create a debugger interface instance.
1074
1075
1076 @param debugServer reference to the debug server
1077 @type DebugServer
1078 @param passive flag indicating passive connection mode
1079 @type bool
1080 @return instantiated debugger interface
1081 @rtype DebuggerInterfacePython
1082 """
1083 return DebuggerInterfacePython(debugServer, passive)
1084
1085
1086 def getRegistryData():
1087 """
1088 Module function to get characterizing data for the debugger interface.
1089
1090 @return tuple containing client type, client capabilities, client file
1091 type associations and reference to creation function
1092 @rtype tuple of (str, int, list of str, function)
1093 """
1094 exts = []
1095 for ext in Preferences.getDebugger("PythonExtensions").split():
1096 if ext.startswith("."):
1097 exts.append(ext)
1098 else:
1099 exts.append(".{0}".format(ext))
1100
1101 if exts and Preferences.getDebugger("PythonInterpreter"):
1102 return ["Python2", ClientDefaultCapabilities, exts,
1103 createDebuggerInterfacePython]
1104 else:
1105 return ["", 0, [], None]

eric ide

mercurial