##// END OF EJS Templates
Merge pull request #2544 from bfroehle/tracer_recursion...
Bradley M. Froehle -
r8955:ca398153 merge
parent child Browse files
Show More
@@ -1,557 +1,566 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 6 the command line completion of other programs which include this isn't
7 7 damaged.
8 8
9 9 In the future, this class will be expanded with improvements over the standard
10 10 pdb.
11 11
12 12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 13 changes. Licensing should therefore be under the standard Python terms. For
14 14 details on the PSF (Python Software Foundation) standard license, see:
15 15
16 16 http://www.python.org/2.2.3/license.html"""
17 17
18 18 #*****************************************************************************
19 19 #
20 20 # This file is licensed under the PSF license.
21 21 #
22 22 # Copyright (C) 2001 Python Software Foundation, www.python.org
23 23 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
24 24 #
25 25 #
26 26 #*****************************************************************************
27 27 from __future__ import print_function
28 28
29 29 import bdb
30 import functools
30 31 import linecache
31 32 import sys
32 33
33 34 from IPython.utils import PyColorize, ulinecache
34 35 from IPython.core import ipapi
35 36 from IPython.utils import coloransi, io, openpy, py3compat
36 37 from IPython.core.excolors import exception_colors
37 38
38 39 # See if we can use pydb.
39 40 has_pydb = False
40 41 prompt = 'ipdb> '
41 42 #We have to check this directly from sys.argv, config struct not yet available
42 43 if '--pydb' in sys.argv:
43 44 try:
44 45 import pydb
45 46 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
46 47 # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
47 48 # better protect against it.
48 49 has_pydb = True
49 50 except ImportError:
50 51 print("Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available")
51 52
52 53 if has_pydb:
53 54 from pydb import Pdb as OldPdb
54 55 #print "Using pydb for %run -d and post-mortem" #dbg
55 56 prompt = 'ipydb> '
56 57 else:
57 58 from pdb import Pdb as OldPdb
58 59
59 60 # Allow the set_trace code to operate outside of an ipython instance, even if
60 61 # it does so with some limitations. The rest of this support is implemented in
61 62 # the Tracer constructor.
62 def BdbQuit_excepthook(et,ev,tb):
63 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
64 """Exception hook which handles `BdbQuit` exceptions.
65
66 All other exceptions are processed using the `excepthook`
67 parameter.
68 """
63 69 if et==bdb.BdbQuit:
64 70 print('Exiting Debugger.')
71 elif excepthook is not None:
72 excepthook(et, ev, tb)
65 73 else:
74 # Backwards compatibility. Raise deprecation warning?
66 75 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
67 76
68 77 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
69 78 print('Exiting Debugger.')
70 79
71 80
72 81 class Tracer(object):
73 82 """Class for local debugging, similar to pdb.set_trace.
74 83
75 84 Instances of this class, when called, behave like pdb.set_trace, but
76 85 providing IPython's enhanced capabilities.
77 86
78 87 This is implemented as a class which must be initialized in your own code
79 88 and not as a standalone function because we need to detect at runtime
80 89 whether IPython is already active or not. That detection is done in the
81 90 constructor, ensuring that this code plays nicely with a running IPython,
82 91 while functioning acceptably (though with limitations) if outside of it.
83 92 """
84 93
85 94 def __init__(self,colors=None):
86 95 """Create a local debugger instance.
87 96
88 97 :Parameters:
89 98
90 99 - `colors` (None): a string containing the name of the color scheme to
91 100 use, it must be one of IPython's valid color schemes. If not given, the
92 101 function will default to the current IPython scheme when running inside
93 102 IPython, and to 'NoColor' otherwise.
94 103
95 104 Usage example:
96 105
97 106 from IPython.core.debugger import Tracer; debug_here = Tracer()
98 107
99 108 ... later in your code
100 109 debug_here() # -> will open up the debugger at that point.
101 110
102 111 Once the debugger activates, you can use all of its regular commands to
103 112 step through code, set breakpoints, etc. See the pdb documentation
104 113 from the Python standard library for usage details.
105 114 """
106 115
107 116 try:
108 117 ip = get_ipython()
109 118 except NameError:
110 119 # Outside of ipython, we set our own exception hook manually
111 BdbQuit_excepthook.excepthook_ori = sys.excepthook
112 sys.excepthook = BdbQuit_excepthook
120 sys.excepthook = functools.partial(BdbQuit_excepthook,
121 excepthook=sys.excepthook)
113 122 def_colors = 'NoColor'
114 123 try:
115 124 # Limited tab completion support
116 125 import readline
117 126 readline.parse_and_bind('tab: complete')
118 127 except ImportError:
119 128 pass
120 129 else:
121 130 # In ipython, we use its custom exception handler mechanism
122 131 def_colors = ip.colors
123 132 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
124 133
125 134 if colors is None:
126 135 colors = def_colors
127 136
128 137 # The stdlib debugger internally uses a modified repr from the `repr`
129 138 # module, that limits the length of printed strings to a hardcoded
130 139 # limit of 30 characters. That much trimming is too aggressive, let's
131 140 # at least raise that limit to 80 chars, which should be enough for
132 141 # most interactive uses.
133 142 try:
134 143 from repr import aRepr
135 144 aRepr.maxstring = 80
136 145 except:
137 146 # This is only a user-facing convenience, so any error we encounter
138 147 # here can be warned about but can be otherwise ignored. These
139 # printouts will tell us about problems if this API changes
148 # printouts will tell us about problems if this API changes
140 149 import traceback
141 150 traceback.print_exc()
142 151
143 152 self.debugger = Pdb(colors)
144 153
145 154 def __call__(self):
146 155 """Starts an interactive debugger at the point where called.
147 156
148 157 This is similar to the pdb.set_trace() function from the std lib, but
149 158 using IPython's enhanced debugger."""
150 159
151 160 self.debugger.set_trace(sys._getframe().f_back)
152 161
153 162
154 163 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
155 164 """Make new_fn have old_fn's doc string. This is particularly useful
156 165 for the do_... commands that hook into the help system.
157 166 Adapted from from a comp.lang.python posting
158 167 by Duncan Booth."""
159 168 def wrapper(*args, **kw):
160 169 return new_fn(*args, **kw)
161 170 if old_fn.__doc__:
162 171 wrapper.__doc__ = old_fn.__doc__ + additional_text
163 172 return wrapper
164 173
165 174
166 175 def _file_lines(fname):
167 176 """Return the contents of a named file as a list of lines.
168 177
169 178 This function never raises an IOError exception: if the file can't be
170 179 read, it simply returns an empty list."""
171 180
172 181 try:
173 182 outfile = open(fname)
174 183 except IOError:
175 184 return []
176 185 else:
177 186 out = outfile.readlines()
178 187 outfile.close()
179 188 return out
180 189
181 190
182 191 class Pdb(OldPdb):
183 192 """Modified Pdb class, does not load readline."""
184 193
185 194 def __init__(self,color_scheme='NoColor',completekey=None,
186 195 stdin=None, stdout=None):
187 196
188 197 # Parent constructor:
189 198 if has_pydb and completekey is None:
190 199 OldPdb.__init__(self,stdin=stdin,stdout=io.stdout)
191 200 else:
192 201 OldPdb.__init__(self,completekey,stdin,stdout)
193 202
194 203 self.prompt = prompt # The default prompt is '(Pdb)'
195 204
196 205 # IPython changes...
197 206 self.is_pydb = has_pydb
198 207
199 208 self.shell = ipapi.get()
200 209
201 210 if self.is_pydb:
202 211
203 212 # interactiveshell.py's ipalias seems to want pdb's checkline
204 213 # which located in pydb.fn
205 214 import pydb.fns
206 215 self.checkline = lambda filename, lineno: \
207 216 pydb.fns.checkline(self, filename, lineno)
208 217
209 218 self.curframe = None
210 219 self.do_restart = self.new_do_restart
211 220
212 221 self.old_all_completions = self.shell.Completer.all_completions
213 222 self.shell.Completer.all_completions=self.all_completions
214 223
215 224 self.do_list = decorate_fn_with_doc(self.list_command_pydb,
216 225 OldPdb.do_list)
217 226 self.do_l = self.do_list
218 227 self.do_frame = decorate_fn_with_doc(self.new_do_frame,
219 228 OldPdb.do_frame)
220 229
221 230 self.aliases = {}
222 231
223 232 # Create color table: we copy the default one from the traceback
224 233 # module and add a few attributes needed for debugging
225 234 self.color_scheme_table = exception_colors()
226 235
227 236 # shorthands
228 237 C = coloransi.TermColors
229 238 cst = self.color_scheme_table
230 239
231 240 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
232 241 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
233 242
234 243 cst['Linux'].colors.breakpoint_enabled = C.LightRed
235 244 cst['Linux'].colors.breakpoint_disabled = C.Red
236 245
237 246 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
238 247 cst['LightBG'].colors.breakpoint_disabled = C.Red
239 248
240 249 self.set_colors(color_scheme)
241 250
242 251 # Add a python parser so we can syntax highlight source while
243 252 # debugging.
244 253 self.parser = PyColorize.Parser()
245 254
246 255 def set_colors(self, scheme):
247 256 """Shorthand access to the color table scheme selector method."""
248 257 self.color_scheme_table.set_active_scheme(scheme)
249 258
250 259 def interaction(self, frame, traceback):
251 260 self.shell.set_completer_frame(frame)
252 261 OldPdb.interaction(self, frame, traceback)
253 262
254 263 def new_do_up(self, arg):
255 264 OldPdb.do_up(self, arg)
256 265 self.shell.set_completer_frame(self.curframe)
257 266 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
258 267
259 268 def new_do_down(self, arg):
260 269 OldPdb.do_down(self, arg)
261 270 self.shell.set_completer_frame(self.curframe)
262 271
263 272 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
264 273
265 274 def new_do_frame(self, arg):
266 275 OldPdb.do_frame(self, arg)
267 276 self.shell.set_completer_frame(self.curframe)
268 277
269 278 def new_do_quit(self, arg):
270 279
271 280 if hasattr(self, 'old_all_completions'):
272 281 self.shell.Completer.all_completions=self.old_all_completions
273 282
274 283
275 284 return OldPdb.do_quit(self, arg)
276 285
277 286 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
278 287
279 288 def new_do_restart(self, arg):
280 289 """Restart command. In the context of ipython this is exactly the same
281 290 thing as 'quit'."""
282 291 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
283 292 return self.do_quit(arg)
284 293
285 294 def postloop(self):
286 295 self.shell.set_completer_frame(None)
287 296
288 297 def print_stack_trace(self):
289 298 try:
290 299 for frame_lineno in self.stack:
291 300 self.print_stack_entry(frame_lineno, context = 5)
292 301 except KeyboardInterrupt:
293 302 pass
294 303
295 304 def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
296 305 context = 3):
297 306 #frame, lineno = frame_lineno
298 307 print(self.format_stack_entry(frame_lineno, '', context), file=io.stdout)
299 308
300 309 # vds: >>
301 310 frame, lineno = frame_lineno
302 311 filename = frame.f_code.co_filename
303 312 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
304 313 # vds: <<
305 314
306 315 def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3):
307 316 import repr
308 317
309 318 ret = []
310 319
311 320 Colors = self.color_scheme_table.active_colors
312 321 ColorsNormal = Colors.Normal
313 322 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
314 323 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
315 324 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
316 325 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
317 326 ColorsNormal)
318 327
319 328 frame, lineno = frame_lineno
320 329
321 330 return_value = ''
322 331 if '__return__' in frame.f_locals:
323 332 rv = frame.f_locals['__return__']
324 333 #return_value += '->'
325 334 return_value += repr.repr(rv) + '\n'
326 335 ret.append(return_value)
327 336
328 337 #s = filename + '(' + `lineno` + ')'
329 338 filename = self.canonic(frame.f_code.co_filename)
330 339 link = tpl_link % py3compat.cast_unicode(filename)
331 340
332 341 if frame.f_code.co_name:
333 342 func = frame.f_code.co_name
334 343 else:
335 344 func = "<lambda>"
336 345
337 346 call = ''
338 347 if func != '?':
339 348 if '__args__' in frame.f_locals:
340 349 args = repr.repr(frame.f_locals['__args__'])
341 350 else:
342 351 args = '()'
343 352 call = tpl_call % (func, args)
344 353
345 354 # The level info should be generated in the same format pdb uses, to
346 355 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
347 356 if frame is self.curframe:
348 357 ret.append('> ')
349 358 else:
350 359 ret.append(' ')
351 360 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
352 361
353 362 start = lineno - 1 - context//2
354 363 lines = ulinecache.getlines(filename)
355 364 start = min(start, len(lines) - context)
356 365 start = max(start, 0)
357 366 lines = lines[start : start + context]
358 367
359 368 for i,line in enumerate(lines):
360 369 show_arrow = (start + 1 + i == lineno)
361 370 linetpl = (frame is self.curframe or show_arrow) \
362 371 and tpl_line_em \
363 372 or tpl_line
364 373 ret.append(self.__format_line(linetpl, filename,
365 374 start + 1 + i, line,
366 375 arrow = show_arrow) )
367 376 return ''.join(ret)
368 377
369 378 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
370 379 bp_mark = ""
371 380 bp_mark_color = ""
372 381
373 382 scheme = self.color_scheme_table.active_scheme_name
374 383 new_line, err = self.parser.format2(line, 'str', scheme)
375 384 if not err: line = new_line
376 385
377 386 bp = None
378 387 if lineno in self.get_file_breaks(filename):
379 388 bps = self.get_breaks(filename, lineno)
380 389 bp = bps[-1]
381 390
382 391 if bp:
383 392 Colors = self.color_scheme_table.active_colors
384 393 bp_mark = str(bp.number)
385 394 bp_mark_color = Colors.breakpoint_enabled
386 395 if not bp.enabled:
387 396 bp_mark_color = Colors.breakpoint_disabled
388 397
389 398 numbers_width = 7
390 399 if arrow:
391 400 # This is the line with the error
392 401 pad = numbers_width - len(str(lineno)) - len(bp_mark)
393 402 if pad >= 3:
394 403 marker = '-'*(pad-3) + '-> '
395 404 elif pad == 2:
396 405 marker = '> '
397 406 elif pad == 1:
398 407 marker = '>'
399 408 else:
400 409 marker = ''
401 410 num = '%s%s' % (marker, str(lineno))
402 411 line = tpl_line % (bp_mark_color + bp_mark, num, line)
403 412 else:
404 413 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
405 414 line = tpl_line % (bp_mark_color + bp_mark, num, line)
406 415
407 416 return line
408 417
409 418 def list_command_pydb(self, arg):
410 419 """List command to use if we have a newer pydb installed"""
411 420 filename, first, last = OldPdb.parse_list_cmd(self, arg)
412 421 if filename is not None:
413 422 self.print_list_lines(filename, first, last)
414 423
415 424 def print_list_lines(self, filename, first, last):
416 425 """The printing (as opposed to the parsing part of a 'list'
417 426 command."""
418 427 try:
419 428 Colors = self.color_scheme_table.active_colors
420 429 ColorsNormal = Colors.Normal
421 430 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
422 431 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
423 432 src = []
424 433 if filename == "<string>" and hasattr(self, "_exec_filename"):
425 434 filename = self._exec_filename
426
435
427 436 for lineno in range(first, last+1):
428 437 line = ulinecache.getline(filename, lineno)
429 438 if not line:
430 439 break
431 440
432 441 if lineno == self.curframe.f_lineno:
433 442 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
434 443 else:
435 444 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
436 445
437 446 src.append(line)
438 447 self.lineno = lineno
439 448
440 449 print(''.join(src), file=io.stdout)
441 450
442 451 except KeyboardInterrupt:
443 452 pass
444 453
445 454 def do_list(self, arg):
446 455 self.lastcmd = 'list'
447 456 last = None
448 457 if arg:
449 458 try:
450 459 x = eval(arg, {}, {})
451 460 if type(x) == type(()):
452 461 first, last = x
453 462 first = int(first)
454 463 last = int(last)
455 464 if last < first:
456 465 # Assume it's a count
457 466 last = first + last
458 467 else:
459 468 first = max(1, int(x) - 5)
460 469 except:
461 470 print('*** Error in argument:', repr(arg))
462 471 return
463 472 elif self.lineno is None:
464 473 first = max(1, self.curframe.f_lineno - 5)
465 474 else:
466 475 first = self.lineno + 1
467 476 if last is None:
468 477 last = first + 10
469 478 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
470 479
471 480 # vds: >>
472 481 lineno = first
473 482 filename = self.curframe.f_code.co_filename
474 483 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
475 484 # vds: <<
476 485
477 486 do_l = do_list
478 487
479 488 def do_pdef(self, arg):
480 489 """Print the call signature for any callable object.
481 490
482 491 The debugger interface to %pdef"""
483 492 namespaces = [('Locals', self.curframe.f_locals),
484 493 ('Globals', self.curframe.f_globals)]
485 494 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
486 495
487 496 def do_pdoc(self, arg):
488 497 """Print the docstring for an object.
489 498
490 499 The debugger interface to %pdoc."""
491 500 namespaces = [('Locals', self.curframe.f_locals),
492 501 ('Globals', self.curframe.f_globals)]
493 502 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
494 503
495 504 def do_pfile(self, arg):
496 505 """Print (or run through pager) the file where an object is defined.
497 506
498 507 The debugger interface to %pfile.
499 508 """
500 509 namespaces = [('Locals', self.curframe.f_locals),
501 510 ('Globals', self.curframe.f_globals)]
502 511 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces)
503 512
504 513 def do_pinfo(self, arg):
505 514 """Provide detailed information about an object.
506 515
507 516 The debugger interface to %pinfo, i.e., obj?."""
508 517 namespaces = [('Locals', self.curframe.f_locals),
509 518 ('Globals', self.curframe.f_globals)]
510 519 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces)
511 520
512 521 def do_pinfo2(self, arg):
513 522 """Provide extra detailed information about an object.
514 523
515 524 The debugger interface to %pinfo2, i.e., obj??."""
516 525 namespaces = [('Locals', self.curframe.f_locals),
517 526 ('Globals', self.curframe.f_globals)]
518 527 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces)
519 528
520 529 def do_psource(self, arg):
521 530 """Print (or run through pager) the source code for an object."""
522 531 namespaces = [('Locals', self.curframe.f_locals),
523 532 ('Globals', self.curframe.f_globals)]
524 533 self.shell.find_line_magic('psource')(arg, namespaces=namespaces)
525 534
526 535 def checkline(self, filename, lineno):
527 536 """Check whether specified line seems to be executable.
528 537
529 538 Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank
530 539 line or EOF). Warning: testing is not comprehensive.
531 540 """
532 541 #######################################################################
533 542 # XXX Hack! Use python-2.5 compatible code for this call, because with
534 543 # all of our changes, we've drifted from the pdb api in 2.6. For now,
535 544 # changing:
536 545 #
537 546 #line = linecache.getline(filename, lineno, self.curframe.f_globals)
538 547 # to:
539 548 #
540 549 line = linecache.getline(filename, lineno)
541 550 #
542 551 # does the trick. But in reality, we need to fix this by reconciling
543 552 # our updates with the new Pdb APIs in Python 2.6.
544 553 #
545 554 # End hack. The rest of this method is copied verbatim from 2.6 pdb.py
546 555 #######################################################################
547 556
548 557 if not line:
549 558 print('End of file', file=self.stdout)
550 559 return 0
551 560 line = line.strip()
552 561 # Don't allow setting breakpoint at a blank line
553 562 if (not line or (line[0] == '#') or
554 563 (line[:3] == '"""') or line[:3] == "'''"):
555 564 print('*** Blank or comment', file=self.stdout)
556 565 return 0
557 566 return lineno
General Comments 0
You need to be logged in to leave comments. Login now