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