|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Ruby debugger interface for the debug server. |
|
8 """ |
|
9 |
|
10 import sys |
|
11 import os |
|
12 import socket |
|
13 import subprocess |
|
14 |
|
15 from PyQt4.QtCore import * |
|
16 from PyQt4.QtGui import QMessageBox |
|
17 |
|
18 from E4Gui.E4Application import e4App |
|
19 |
|
20 from DebugProtocol import * |
|
21 import DebugClientCapabilities |
|
22 |
|
23 import Preferences |
|
24 import Utilities |
|
25 |
|
26 from eric4config import getConfig |
|
27 |
|
28 |
|
29 ClientDefaultCapabilities = \ |
|
30 DebugClientCapabilities.HasDebugger | \ |
|
31 DebugClientCapabilities.HasShell | \ |
|
32 DebugClientCapabilities.HasInterpreter | \ |
|
33 DebugClientCapabilities.HasCompleter |
|
34 |
|
35 ClientTypeAssociations = [".rb"] |
|
36 |
|
37 def getRegistryData(): |
|
38 """ |
|
39 Module function to get characterising data for the debugger interface. |
|
40 |
|
41 @return list of the following data. Client type (string), client |
|
42 capabilities (integer), client type association (list of strings) |
|
43 """ |
|
44 return ["Ruby", ClientDefaultCapabilities, ClientTypeAssociations] |
|
45 |
|
46 class DebuggerInterfaceRuby(QObject): |
|
47 """ |
|
48 Class implementing the Ruby debugger interface for the debug server. |
|
49 """ |
|
50 def __init__(self, debugServer, passive): |
|
51 """ |
|
52 Constructor |
|
53 |
|
54 @param debugServer reference to the debug server (DebugServer) |
|
55 @param passive flag indicating passive connection mode (boolean) |
|
56 """ |
|
57 QObject.__init__(self) |
|
58 |
|
59 self.__isNetworked = True |
|
60 self.__autoContinue = not passive |
|
61 |
|
62 self.debugServer = debugServer |
|
63 self.passive = passive |
|
64 self.process = None |
|
65 |
|
66 self.qsock = None |
|
67 self.queue = [] |
|
68 # set default values for capabilities of clients |
|
69 self.clientCapabilities = ClientDefaultCapabilities |
|
70 |
|
71 self.codec = QTextCodec.codecForName(str(Preferences.getSystem("StringEncoding"))) |
|
72 |
|
73 if passive: |
|
74 # set translation function |
|
75 if Preferences.getDebugger("PathTranslation"): |
|
76 self.translateRemote = \ |
|
77 Preferences.getDebugger("PathTranslationRemote") |
|
78 self.translateLocal = \ |
|
79 Preferences.getDebugger("PathTranslationLocal") |
|
80 self.translate = self.__remoteTranslation |
|
81 else: |
|
82 self.translate = self.__identityTranslation |
|
83 |
|
84 def __identityTranslation(self, fn, remote2local = True): |
|
85 """ |
|
86 Private method to perform the identity path translation. |
|
87 |
|
88 @param fn filename to be translated (string) |
|
89 @param remote2local flag indicating the direction of translation |
|
90 (False = local to remote, True = remote to local [default]) |
|
91 @return translated filename (string) |
|
92 """ |
|
93 return fn |
|
94 |
|
95 def __remoteTranslation(self, fn, remote2local = True): |
|
96 """ |
|
97 Private method to perform the path translation. |
|
98 |
|
99 @param fn filename to be translated (string) |
|
100 @param remote2local flag indicating the direction of translation |
|
101 (False = local to remote, True = remote to local [default]) |
|
102 @return translated filename (string) |
|
103 """ |
|
104 if remote2local: |
|
105 return fn.replace(self.translateRemote, self.translateLocal) |
|
106 else: |
|
107 return fn.replace(self.translateLocal, self.translateRemote) |
|
108 |
|
109 def __startProcess(self, program, arguments, environment = None): |
|
110 """ |
|
111 Private method to start the debugger client process. |
|
112 |
|
113 @param program name of the executable to start (string) |
|
114 @param arguments arguments to be passed to the program (list of string) |
|
115 @param environment dictionary of environment settings to pass (dict of string) |
|
116 @return the process object (QProcess) or None |
|
117 """ |
|
118 proc = QProcess() |
|
119 if environment is not None: |
|
120 env = [] |
|
121 for key, value in environment.items(): |
|
122 env.append("%s=%s" % (key, value)) |
|
123 proc.setEnvironment(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 Ruby 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) and a flag to indicate |
|
141 a network connection (boolean) |
|
142 """ |
|
143 interpreter = Preferences.getDebugger("RubyInterpreter") |
|
144 if interpreter == "": |
|
145 interpreter = "/usr/bin/ruby" |
|
146 debugClient = os.path.join(getConfig('ericDir'), |
|
147 "DebugClients", "Ruby", "DebugClient.rb") |
|
148 |
|
149 redirect = str(Preferences.getDebugger("RubyRedirect")) |
|
150 |
|
151 if Preferences.getDebugger("RemoteDbgEnabled"): |
|
152 ipaddr = self.debugServer.getHostAddress(False)[0] |
|
153 rexec = Preferences.getDebugger("RemoteExecution") |
|
154 rhost = Preferences.getDebugger("RemoteHost") |
|
155 if rhost == "": |
|
156 rhost = "localhost" |
|
157 if rexec: |
|
158 args = Utilities.parseOptionString(rexec) + \ |
|
159 [rhost, interpreter, os.path.abspath(debugClient), |
|
160 str(port), redirect, ipaddr] |
|
161 args[0] = Utilities.getExecutablePath(args[0]) |
|
162 process = self.__startProcess(args[0], args[1:]) |
|
163 if process is None: |
|
164 QMessageBox.critical(None, |
|
165 self.trUtf8("Start Debugger"), |
|
166 self.trUtf8(\ |
|
167 """<p>The debugger backend could not be started.</p>""")) |
|
168 |
|
169 # set translation function |
|
170 if Preferences.getDebugger("PathTranslation"): |
|
171 self.translateRemote = \ |
|
172 Preferences.getDebugger("PathTranslationRemote") |
|
173 self.translateLocal = \ |
|
174 Preferences.getDebugger("PathTranslationLocal") |
|
175 self.translate = self.__remoteTranslation |
|
176 else: |
|
177 self.translate = self.__identityTranslation |
|
178 return process, self.__isNetworked |
|
179 |
|
180 # set translation function |
|
181 self.translate = self.__identityTranslation |
|
182 |
|
183 # setup the environment for the debugger |
|
184 if Preferences.getDebugger("DebugEnvironmentReplace"): |
|
185 clientEnv = {} |
|
186 else: |
|
187 clientEnv = os.environ.copy() |
|
188 envlist = Utilities.parseEnvironmentString(\ |
|
189 Preferences.getDebugger("DebugEnvironment")) |
|
190 for el in envlist: |
|
191 try: |
|
192 key, value = el.split('=', 1) |
|
193 if value.startswith('"') or value.startswith("'"): |
|
194 value = value[1:-1] |
|
195 clientEnv[str(key)] = str(value) |
|
196 except UnpackError: |
|
197 pass |
|
198 |
|
199 ipaddr = self.debugServer.getHostAddress(True) |
|
200 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"): |
|
201 ccmd = Preferences.getDebugger("ConsoleDbgCommand") |
|
202 if ccmd: |
|
203 args = Utilities.parseOptionString(ccmd) + \ |
|
204 [interpreter, os.path.abspath(debugClient), |
|
205 str(port), '0', ipaddr] |
|
206 args[0] = Utilities.getExecutablePath(args[0]) |
|
207 process = self.__startProcess(args[0], args[1:], clientEnv) |
|
208 if process is None: |
|
209 QMessageBox.critical(None, |
|
210 self.trUtf8("Start Debugger"), |
|
211 self.trUtf8(\ |
|
212 """<p>The debugger backend could not be started.</p>""")) |
|
213 return process, self.__isNetworked |
|
214 |
|
215 process = self.__startProcess(interpreter, |
|
216 [debugClient, str(port), redirect, ipaddr], |
|
217 clientEnv) |
|
218 if process is None: |
|
219 QMessageBox.critical(None, |
|
220 self.trUtf8("Start Debugger"), |
|
221 self.trUtf8("""<p>The debugger backend could not be started.</p>""")) |
|
222 return process, self.__isNetworked |
|
223 |
|
224 def startRemoteForProject(self, port, runInConsole): |
|
225 """ |
|
226 Public method to start a remote Ruby interpreter for a project. |
|
227 |
|
228 @param port portnumber the debug server is listening on (integer) |
|
229 @param runInConsole flag indicating to start the debugger in a |
|
230 console window (boolean) |
|
231 @return pid of the client process (integer) and a flag to indicate |
|
232 a network connection (boolean) |
|
233 """ |
|
234 project = e4App().getObject("Project") |
|
235 if not project.isDebugPropertiesLoaded(): |
|
236 return None, self.__isNetworked |
|
237 |
|
238 # start debugger with project specific settings |
|
239 interpreter = project.getDebugProperty("INTERPRETER") |
|
240 debugClient = project.getDebugProperty("DEBUGCLIENT") |
|
241 |
|
242 redirect = str(project.getDebugProperty("REDIRECT")) |
|
243 |
|
244 if project.getDebugProperty("REMOTEDEBUGGER"): |
|
245 ipaddr = self.debugServer.getHostAddress(False)[0] |
|
246 rexec = project.getDebugProperty("REMOTECOMMAND") |
|
247 rhost = project.getDebugProperty("REMOTEHOST") |
|
248 if rhost == "": |
|
249 rhost = "localhost" |
|
250 if rexec: |
|
251 args = Utilities.parseOptionString(rexec) + \ |
|
252 [rhost, interpreter, os.path.abspath(debugClient), |
|
253 str(port), redirect, ipaddr] |
|
254 args[0] = Utilities.getExecutablePath(args[0]) |
|
255 process = self.__startProcess(args[0], args[1:]) |
|
256 if process is None: |
|
257 QMessageBox.critical(None, |
|
258 self.trUtf8("Start Debugger"), |
|
259 self.trUtf8(\ |
|
260 """<p>The debugger backend could not be started.</p>""")) |
|
261 # set translation function |
|
262 if project.getDebugProperty("PATHTRANSLATION"): |
|
263 self.translateRemote = project.getDebugProperty("REMOTEPATH") |
|
264 self.translateLocal = project.getDebugProperty("LOCALPATH") |
|
265 self.translate = self.__remoteTranslation |
|
266 else: |
|
267 self.translate = self.__identityTranslation |
|
268 return process, self.__isNetworked |
|
269 |
|
270 # set translation function |
|
271 self.translate = self.__identityTranslation |
|
272 |
|
273 # setup the environment for the debugger |
|
274 if project.getDebugProperty("ENVIRONMENTOVERRIDE"): |
|
275 clientEnv = {} |
|
276 else: |
|
277 clientEnv = os.environ.copy() |
|
278 envlist = Utilities.parseEnvironmentString(\ |
|
279 project.getDebugProperty("ENVIRONMENTSTRING")) |
|
280 for el in envlist: |
|
281 try: |
|
282 key, value = el.split('=', 1) |
|
283 if value.startswith('"') or value.startswith("'"): |
|
284 value = value[1:-1] |
|
285 clientEnv[str(key)] = str(value) |
|
286 except UnpackError: |
|
287 pass |
|
288 |
|
289 ipaddr = self.debugServer.getHostAddress(True) |
|
290 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"): |
|
291 ccmd = project.getDebugProperty("CONSOLECOMMAND") or \ |
|
292 Preferences.getDebugger("ConsoleDbgCommand") |
|
293 if ccmd: |
|
294 args = Utilities.parseOptionString(ccmd) + \ |
|
295 [interpreter, os.path.abspath(debugClient), |
|
296 str(port), '0', ipaddr] |
|
297 args[0] = Utilities.getExecutablePath(args[0]) |
|
298 process = self.__startProcess(args[0], args[1:], clientEnv) |
|
299 if process is None: |
|
300 QMessageBox.critical(None, |
|
301 self.trUtf8("Start Debugger"), |
|
302 self.trUtf8(\ |
|
303 """<p>The debugger backend could not be started.</p>""")) |
|
304 return process, self.__isNetworked |
|
305 |
|
306 process = self.__startProcess(interpreter, |
|
307 [debugClient, str(port), redirect, ipaddr], |
|
308 clientEnv) |
|
309 if process is None: |
|
310 QMessageBox.critical(None, |
|
311 self.trUtf8("Start Debugger"), |
|
312 self.trUtf8("""<p>The debugger backend could not be started.</p>""")) |
|
313 return process, self.__isNetworked |
|
314 |
|
315 def getClientCapabilities(self): |
|
316 """ |
|
317 Public method to retrieve the debug clients capabilities. |
|
318 |
|
319 @return debug client capabilities (integer) |
|
320 """ |
|
321 return self.clientCapabilities |
|
322 |
|
323 def newConnection(self, sock): |
|
324 """ |
|
325 Public slot to handle a new connection. |
|
326 |
|
327 @param sockreference to the socket object (QTcpSocket) |
|
328 @return flag indicating success (boolean) |
|
329 """ |
|
330 # If we already have a connection, refuse this one. It will be closed |
|
331 # automatically. |
|
332 if self.qsock is not None: |
|
333 return False |
|
334 |
|
335 self.connect(sock, SIGNAL('disconnected()'), self.debugServer.startClient) |
|
336 self.connect(sock, SIGNAL('readyRead()'), self.__parseClientLine) |
|
337 |
|
338 self.qsock = sock |
|
339 |
|
340 # Get the remote clients capabilities |
|
341 self.remoteCapabilities() |
|
342 return True |
|
343 |
|
344 def flush(self): |
|
345 """ |
|
346 Public slot to flush the queue. |
|
347 """ |
|
348 # Send commands that were waiting for the connection. |
|
349 for cmd in self.queue: |
|
350 self.qsock.write(cmd.encode('utf8')) |
|
351 |
|
352 self.queue = [] |
|
353 |
|
354 def shutdown(self): |
|
355 """ |
|
356 Public method to cleanly shut down. |
|
357 |
|
358 It closes our socket and shuts down |
|
359 the debug client. (Needed on Win OS) |
|
360 """ |
|
361 if self.qsock is None: |
|
362 return |
|
363 |
|
364 # do not want any slots called during shutdown |
|
365 self.disconnect(self.qsock, SIGNAL('disconnected()'), |
|
366 self.debugServer.startClient) |
|
367 self.disconnect(self.qsock, SIGNAL('readyRead()'), self.__parseClientLine) |
|
368 |
|
369 # close down socket, and shut down client as well. |
|
370 self.__sendCommand('%s\n' % RequestShutdown) |
|
371 self.qsock.flush() |
|
372 |
|
373 self.qsock.close() |
|
374 |
|
375 # reinitialize |
|
376 self.qsock = None |
|
377 self.queue = [] |
|
378 |
|
379 def isConnected(self): |
|
380 """ |
|
381 Public method to test, if a debug client has connected. |
|
382 |
|
383 @return flag indicating the connection status (boolean) |
|
384 """ |
|
385 return self.qsock is not None |
|
386 |
|
387 def remoteEnvironment(self, env): |
|
388 """ |
|
389 Public method to set the environment for a program to debug, run, ... |
|
390 |
|
391 @param env environment settings (dictionary) |
|
392 """ |
|
393 self.__sendCommand('%s%s\n' % (RequestEnv, str(env))) |
|
394 |
|
395 def remoteLoad(self, fn, argv, wd, traceInterpreter = False, autoContinue = True, |
|
396 autoFork = False, forkChild = False): |
|
397 """ |
|
398 Public method to load a new program to debug. |
|
399 |
|
400 @param fn the filename to debug (string) |
|
401 @param argv the commandline arguments to pass to the program (string) |
|
402 @param wd the working directory for the program (string) |
|
403 @keyparam traceInterpreter flag indicating if the interpreter library should be |
|
404 traced as well (boolean) |
|
405 @keyparam autoContinue flag indicating, that the debugger should not stop |
|
406 at the first executable line (boolean) |
|
407 @keyparam autoFork flag indicating the automatic fork mode (boolean) (ignored) |
|
408 @keyparam forkChild flag indicating to debug the child after forking |
|
409 (boolean) (ignored) |
|
410 """ |
|
411 self.__autoContinue = autoContinue |
|
412 |
|
413 wd = self.translate(wd, False) |
|
414 fn = self.translate(os.path.abspath(fn), False) |
|
415 self.__sendCommand('%s%s|%s|%s|%d\n' % \ |
|
416 (RequestLoad, wd, fn, unicode(Utilities.parseOptionString(argv)), |
|
417 traceInterpreter)) |
|
418 |
|
419 def remoteRun(self, fn, argv, wd): |
|
420 """ |
|
421 Public method to load a new program to run. |
|
422 |
|
423 @param fn the filename to run (string) |
|
424 @param argv the commandline arguments to pass to the program (string) |
|
425 @param wd the working directory for the program (string) |
|
426 """ |
|
427 wd = self.translate(wd, False) |
|
428 fn = self.translate(os.path.abspath(fn), False) |
|
429 self.__sendCommand('%s%s|%s|%s\n' % \ |
|
430 (RequestRun, wd, fn, unicode(Utilities.parseOptionString(argv)))) |
|
431 |
|
432 def remoteCoverage(self, fn, argv, wd, erase = False): |
|
433 """ |
|
434 Public method to load a new program to collect coverage data. |
|
435 |
|
436 @param fn the filename to run (string) |
|
437 @param argv the commandline arguments to pass to the program (string) |
|
438 @param wd the working directory for the program (string) |
|
439 @keyparam erase flag indicating that coverage info should be |
|
440 cleared first (boolean) |
|
441 """ |
|
442 raise NotImplementedError("Interface not available.") |
|
443 |
|
444 def remoteProfile(self, fn, argv, wd, erase = False): |
|
445 """ |
|
446 Public method to load a new program to collect profiling data. |
|
447 |
|
448 @param fn the filename to run (string) |
|
449 @param argv the commandline arguments to pass to the program (string) |
|
450 @param wd the working directory for the program (string) |
|
451 @keyparam erase flag indicating that timing info should be cleared first (boolean) |
|
452 """ |
|
453 raise NotImplementedError("Interface not available.") |
|
454 |
|
455 def remoteStatement(self, stmt): |
|
456 """ |
|
457 Public method to execute a Ruby statement. |
|
458 |
|
459 @param stmt the Ruby statement to execute (string). It |
|
460 should not have a trailing newline. |
|
461 """ |
|
462 self.__sendCommand('%s\n' % stmt) |
|
463 self.__sendCommand(RequestOK + '\n') |
|
464 |
|
465 def remoteStep(self): |
|
466 """ |
|
467 Public method to single step the debugged program. |
|
468 """ |
|
469 self.__sendCommand(RequestStep + '\n') |
|
470 |
|
471 def remoteStepOver(self): |
|
472 """ |
|
473 Public method to step over the debugged program. |
|
474 """ |
|
475 self.__sendCommand(RequestStepOver + '\n') |
|
476 |
|
477 def remoteStepOut(self): |
|
478 """ |
|
479 Public method to step out the debugged program. |
|
480 """ |
|
481 self.__sendCommand(RequestStepOut + '\n') |
|
482 |
|
483 def remoteStepQuit(self): |
|
484 """ |
|
485 Public method to stop the debugged program. |
|
486 """ |
|
487 self.__sendCommand(RequestStepQuit + '\n') |
|
488 |
|
489 def remoteContinue(self, special = False): |
|
490 """ |
|
491 Public method to continue the debugged program. |
|
492 |
|
493 @param special flag indicating a special continue operation (boolean) |
|
494 """ |
|
495 self.__sendCommand('%s%d\n' % (RequestContinue, special)) |
|
496 |
|
497 def remoteBreakpoint(self, fn, line, set, cond = None, temp = False): |
|
498 """ |
|
499 Public method to set or clear a breakpoint. |
|
500 |
|
501 @param fn filename the breakpoint belongs to (string) |
|
502 @param line linenumber of the breakpoint (int) |
|
503 @param set flag indicating setting or resetting a breakpoint (boolean) |
|
504 @param cond condition of the breakpoint (string) |
|
505 @param temp flag indicating a temporary breakpoint (boolean) |
|
506 """ |
|
507 fn = self.translate(fn, False) |
|
508 self.__sendCommand('%s%s@@%d@@%d@@%d@@%s\n' % \ |
|
509 (RequestBreak, fn, line, temp, set, cond)) |
|
510 |
|
511 def remoteBreakpointEnable(self, fn, line, enable): |
|
512 """ |
|
513 Public method to enable or disable a breakpoint. |
|
514 |
|
515 @param fn filename the breakpoint belongs to (string) |
|
516 @param line linenumber of the breakpoint (int) |
|
517 @param enable flag indicating enabling or disabling a breakpoint (boolean) |
|
518 """ |
|
519 fn = self.translate(fn, False) |
|
520 self.__sendCommand('%s%s,%d,%d\n' % (RequestBreakEnable, fn, line, enable)) |
|
521 |
|
522 def remoteBreakpointIgnore(self, fn, line, count): |
|
523 """ |
|
524 Public method to ignore a breakpoint the next couple of occurrences. |
|
525 |
|
526 @param fn filename the breakpoint belongs to (string) |
|
527 @param line linenumber of the breakpoint (int) |
|
528 @param count number of occurrences to ignore (int) |
|
529 """ |
|
530 fn = self.translate(fn, False) |
|
531 self.__sendCommand('%s%s,%d,%d\n' % (RequestBreakIgnore, fn, line, count)) |
|
532 |
|
533 def remoteWatchpoint(self, cond, set, temp = False): |
|
534 """ |
|
535 Public method to set or clear a watch expression. |
|
536 |
|
537 @param cond expression of the watch expression (string) |
|
538 @param set flag indicating setting or resetting a watch expression (boolean) |
|
539 @param temp flag indicating a temporary watch expression (boolean) |
|
540 """ |
|
541 # cond is combination of cond and special (s. watch expression viewer) |
|
542 self.__sendCommand('%s%s@@%d@@%d\n' % (RequestWatch, cond, temp, set)) |
|
543 |
|
544 def remoteWatchpointEnable(self, cond, enable): |
|
545 """ |
|
546 Public method to enable or disable a watch expression. |
|
547 |
|
548 @param cond expression of the watch expression (string) |
|
549 @param enable flag indicating enabling or disabling a watch expression (boolean) |
|
550 """ |
|
551 # cond is combination of cond and special (s. watch expression viewer) |
|
552 self.__sendCommand('%s%s,%d\n' % (RequestWatchEnable, cond, enable)) |
|
553 |
|
554 def remoteWatchpointIgnore(self, cond, count): |
|
555 """ |
|
556 Public method to ignore a watch expression the next couple of occurrences. |
|
557 |
|
558 @param cond expression of the watch expression (string) |
|
559 @param count number of occurrences to ignore (int) |
|
560 """ |
|
561 # cond is combination of cond and special (s. watch expression viewer) |
|
562 self.__sendCommand('%s%s,%d\n' % (RequestWatchIgnore, cond, count)) |
|
563 |
|
564 def remoteRawInput(self,s): |
|
565 """ |
|
566 Public method to send the raw input to the debugged program. |
|
567 |
|
568 @param s the raw input (string) |
|
569 """ |
|
570 self.__sendCommand(s + '\n') |
|
571 |
|
572 def remoteThreadList(self): |
|
573 """ |
|
574 Public method to request the list of threads from the client. |
|
575 """ |
|
576 return |
|
577 |
|
578 def remoteSetThread(self, tid): |
|
579 """ |
|
580 Public method to request to set the given thread as current thread. |
|
581 |
|
582 @param tid id of the thread (integer) |
|
583 """ |
|
584 return |
|
585 |
|
586 def remoteClientVariables(self, scope, filter, framenr = 0): |
|
587 """ |
|
588 Public method to request the variables of the debugged program. |
|
589 |
|
590 @param scope the scope of the variables (0 = local, 1 = global) |
|
591 @param filter list of variable types to filter out (list of int) |
|
592 @param framenr framenumber of the variables to retrieve (int) |
|
593 """ |
|
594 self.__sendCommand('%s%d, %d, %s\n' % \ |
|
595 (RequestVariables, framenr, scope, unicode(filter))) |
|
596 |
|
597 def remoteClientVariable(self, scope, filter, var, framenr = 0): |
|
598 """ |
|
599 Public method to request the variables of the debugged program. |
|
600 |
|
601 @param scope the scope of the variables (0 = local, 1 = global) |
|
602 @param filter list of variable types to filter out (list of int) |
|
603 @param var list encoded name of variable to retrieve (string) |
|
604 @param framenr framenumber of the variables to retrieve (int) |
|
605 """ |
|
606 self.__sendCommand('%s%s, %d, %d, %s\n' % \ |
|
607 (RequestVariable, unicode(var), framenr, scope, str(filter))) |
|
608 |
|
609 def remoteClientSetFilter(self, scope, filter): |
|
610 """ |
|
611 Public method to set a variables filter list. |
|
612 |
|
613 @param scope the scope of the variables (0 = local, 1 = global) |
|
614 @param filter regexp string for variable names to filter out (string) |
|
615 """ |
|
616 self.__sendCommand('%s%d, "%s"\n' % (RequestSetFilter, scope, filter)) |
|
617 |
|
618 def remoteEval(self, arg): |
|
619 """ |
|
620 Public method to evaluate arg in the current context of the debugged program. |
|
621 |
|
622 @param arg the arguments to evaluate (string) |
|
623 """ |
|
624 self.__sendCommand('%s%s\n' % (RequestEval, arg)) |
|
625 |
|
626 def remoteExec(self, stmt): |
|
627 """ |
|
628 Public method to execute stmt in the current context of the debugged program. |
|
629 |
|
630 @param stmt statement to execute (string) |
|
631 """ |
|
632 self.__sendCommand('%s%s\n' % (RequestExec, stmt)) |
|
633 |
|
634 def remoteBanner(self): |
|
635 """ |
|
636 Public slot to get the banner info of the remote client. |
|
637 """ |
|
638 self.__sendCommand(RequestBanner + '\n') |
|
639 |
|
640 def remoteCapabilities(self): |
|
641 """ |
|
642 Public slot to get the debug clients capabilities. |
|
643 """ |
|
644 self.__sendCommand(RequestCapabilities + '\n') |
|
645 |
|
646 def remoteCompletion(self, text): |
|
647 """ |
|
648 Public slot to get the a list of possible commandline completions |
|
649 from the remote client. |
|
650 |
|
651 @param text the text to be completed (string) |
|
652 """ |
|
653 self.__sendCommand("%s%s\n" % (RequestCompletion, text)) |
|
654 |
|
655 def remoteUTPrepare(self, fn, tn, tfn, cov, covname, coverase): |
|
656 """ |
|
657 Public method to prepare a new unittest run. |
|
658 |
|
659 @param fn the filename to load (string) |
|
660 @param tn the testname to load (string) |
|
661 @param tfn the test function name to load tests from (string) |
|
662 @param cov flag indicating collection of coverage data is requested |
|
663 @param covname filename to be used to assemble the coverage caches |
|
664 filename |
|
665 @param coverase flag indicating erasure of coverage data is requested |
|
666 """ |
|
667 raise NotImplementedError("Interface not available.") |
|
668 |
|
669 def remoteUTRun(self): |
|
670 """ |
|
671 Public method to start a unittest run. |
|
672 """ |
|
673 raise NotImplementedError("Interface not available.") |
|
674 |
|
675 def remoteUTStop(self): |
|
676 """ |
|
677 public method to stop a unittest run. |
|
678 """ |
|
679 raise NotImplementedError("Interface not available.") |
|
680 |
|
681 def __parseClientLine(self): |
|
682 """ |
|
683 Private method to handle data from the client. |
|
684 """ |
|
685 while self.qsock and self.qsock.canReadLine(): |
|
686 qs = self.qsock.readLine() |
|
687 if self.codec is not None: |
|
688 us = self.codec.fromUnicode(unicode(qs)) |
|
689 else: |
|
690 us = qs |
|
691 line = str(us) |
|
692 if line.endswith(EOT): |
|
693 line = line[:-len(EOT)] |
|
694 if not line: |
|
695 continue |
|
696 |
|
697 ## print "Server: ", line ##debug |
|
698 |
|
699 eoc = line.find('<') + 1 |
|
700 |
|
701 # Deal with case where user has written directly to stdout |
|
702 # or stderr, but not line terminated and we stepped over the |
|
703 # write call, in that case the >line< will not be the first |
|
704 # string read from the socket... |
|
705 boc = line.find('>') |
|
706 if boc > 0 and eoc > boc: |
|
707 self.debugServer.clientOutput(line[:boc]) |
|
708 line = line[boc:] |
|
709 eoc = line.find('<') + 1 |
|
710 boc = line.find('>') |
|
711 |
|
712 if boc >= 0 and eoc > boc: |
|
713 resp = line[boc:eoc] |
|
714 |
|
715 if resp == ResponseLine: |
|
716 stack = eval(line[eoc:-1]) |
|
717 for s in stack: |
|
718 s[0] = self.translate(s[0], True) |
|
719 cf = stack[0] |
|
720 if self.__autoContinue: |
|
721 self.__autoContinue = False |
|
722 QTimer.singleShot(0, self.remoteContinue) |
|
723 else: |
|
724 self.debugServer.clientLine(cf[0], int(cf[1])) |
|
725 self.debugServer.clientStack(stack) |
|
726 continue |
|
727 |
|
728 if resp == ResponseVariables: |
|
729 vlist = eval(line[eoc:-1]) |
|
730 scope = vlist[0] |
|
731 try: |
|
732 variables = vlist[1:] |
|
733 except IndexError: |
|
734 variables = [] |
|
735 self.debugServer.clientVariables(scope, variables) |
|
736 continue |
|
737 |
|
738 if resp == ResponseVariable: |
|
739 vlist = eval(line[eoc:-1]) |
|
740 scope = vlist[0] |
|
741 try: |
|
742 variables = vlist[1:] |
|
743 except IndexError: |
|
744 variables = [] |
|
745 self.debugServer.clientVariable(scope, variables) |
|
746 continue |
|
747 |
|
748 if resp == ResponseOK: |
|
749 self.debugServer.clientStatement(False) |
|
750 continue |
|
751 |
|
752 if resp == ResponseContinue: |
|
753 self.debugServer.clientStatement(True) |
|
754 continue |
|
755 |
|
756 if resp == ResponseException: |
|
757 exc = line[eoc:-1] |
|
758 exc = self.translate(exc, True) |
|
759 try: |
|
760 exclist = eval(exc) |
|
761 exctype = exclist[0] |
|
762 excmessage = exclist[1] |
|
763 stack = exclist[2:] |
|
764 except (IndexError, ValueError, SyntaxError): |
|
765 exctype = None |
|
766 excmessage = '' |
|
767 stack = [] |
|
768 self.debugServer.clientException(exctype, excmessage, stack) |
|
769 continue |
|
770 |
|
771 if resp == ResponseSyntax: |
|
772 exc = line[eoc:-1] |
|
773 exc = self.translate(exc, True) |
|
774 try: |
|
775 message, (fn, ln, cn) = eval(exc) |
|
776 if fn is None: |
|
777 fn = '' |
|
778 except (IndexError, ValueError): |
|
779 message = None |
|
780 fn = '' |
|
781 ln = 0 |
|
782 cn = 0 |
|
783 self.debugServer.clientSyntaxError(message, fn, ln, cn) |
|
784 continue |
|
785 |
|
786 if resp == ResponseExit: |
|
787 self.debugServer.clientExit(line[eoc:-1]) |
|
788 continue |
|
789 |
|
790 if resp == ResponseClearBreak: |
|
791 fn, lineno = line[eoc:-1].split(',') |
|
792 lineno = int(lineno) |
|
793 fn = self.translate(fn, True) |
|
794 self.debugServer.clientClearBreak(fn, lineno) |
|
795 continue |
|
796 |
|
797 if resp == ResponseClearWatch: |
|
798 cond = line[eoc:-1] |
|
799 self.debugServer.clientClearWatch(cond) |
|
800 continue |
|
801 |
|
802 if resp == ResponseBanner: |
|
803 version, platform, dbgclient = eval(line[eoc:-1]) |
|
804 self.debugServer.clientBanner(version, platform, dbgclient) |
|
805 continue |
|
806 |
|
807 if resp == ResponseCapabilities: |
|
808 cap, clType = eval(line[eoc:-1]) |
|
809 self.clientCapabilities = cap |
|
810 self.debugServer.clientCapabilities(cap, clType) |
|
811 continue |
|
812 |
|
813 if resp == ResponseCompletion: |
|
814 clstring, text = line[eoc:-1].split('||') |
|
815 cl = eval(clstring) |
|
816 self.debugServer.clientCompletionList(cl, text) |
|
817 continue |
|
818 |
|
819 if resp == PassiveStartup: |
|
820 fn, exc = line[eoc:-1].split('|') |
|
821 exc = bool(exc) |
|
822 fn = self.translate(fn, True) |
|
823 self.debugServer.passiveStartUp(fn, exc) |
|
824 continue |
|
825 |
|
826 self.debugServer.clientOutput(line) |
|
827 |
|
828 def __sendCommand(self, cmd): |
|
829 """ |
|
830 Private method to send a single line command to the client. |
|
831 |
|
832 @param cmd command to send to the debug client (string) |
|
833 """ |
|
834 if self.qsock is not None: |
|
835 self.qsock.write(cmd.encode('utf8')) |
|
836 else: |
|
837 self.queue.append(cmd) |