##// END OF EJS Templates
feat(debugger): support . (dot) as an argument of the l(ist) command in ipdb (#14121)...
Matthias Bussonnier -
r28370:03eaa618 merge
parent child Browse files
Show More
@@ -1,997 +1,997 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5
6 6 This is an extension to PDB which adds a number of new features.
7 7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 8 improvements.
9 9
10 10 We also strongly recommend to use this via the `ipdb` package, which provides
11 11 extra configuration options.
12 12
13 13 Among other things, this subclass of PDB:
14 14 - supports many IPython magics like pdef/psource
15 15 - hide frames in tracebacks based on `__tracebackhide__`
16 16 - allows to skip frames based on `__debuggerskip__`
17 17
18 18 The skipping and hiding frames are configurable via the `skip_predicates`
19 19 command.
20 20
21 21 By default, frames from readonly files will be hidden, frames containing
22 22 ``__tracebackhide__=True`` will be hidden.
23 23
24 24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26 26
27 27 >>> def helpers_helper():
28 28 ... pass
29 29 ...
30 30 ... def helper_1():
31 31 ... print("don't step in me")
32 32 ... helpers_helpers() # will be stepped over unless breakpoint set.
33 33 ...
34 34 ...
35 35 ... def helper_2():
36 36 ... print("in me neither")
37 37 ...
38 38
39 39 One can define a decorator that wraps a function between the two helpers:
40 40
41 41 >>> def pdb_skipped_decorator(function):
42 42 ...
43 43 ...
44 44 ... def wrapped_fn(*args, **kwargs):
45 45 ... __debuggerskip__ = True
46 46 ... helper_1()
47 47 ... __debuggerskip__ = False
48 48 ... result = function(*args, **kwargs)
49 49 ... __debuggerskip__ = True
50 50 ... helper_2()
51 51 ... # setting __debuggerskip__ to False again is not necessary
52 52 ... return result
53 53 ...
54 54 ... return wrapped_fn
55 55
56 56 When decorating a function, ipdb will directly step into ``bar()`` by
57 57 default:
58 58
59 59 >>> @foo_decorator
60 60 ... def bar(x, y):
61 61 ... return x * y
62 62
63 63
64 64 You can toggle the behavior with
65 65
66 66 ipdb> skip_predicates debuggerskip false
67 67
68 68 or configure it in your ``.pdbrc``
69 69
70 70
71 71
72 72 License
73 73 -------
74 74
75 75 Modified from the standard pdb.Pdb class to avoid including readline, so that
76 76 the command line completion of other programs which include this isn't
77 77 damaged.
78 78
79 79 In the future, this class will be expanded with improvements over the standard
80 80 pdb.
81 81
82 82 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
83 83 with minor changes. Licensing should therefore be under the standard Python
84 84 terms. For details on the PSF (Python Software Foundation) standard license,
85 85 see:
86 86
87 87 https://docs.python.org/2/license.html
88 88
89 89
90 90 All the changes since then are under the same license as IPython.
91 91
92 92 """
93 93
94 94 #*****************************************************************************
95 95 #
96 96 # This file is licensed under the PSF license.
97 97 #
98 98 # Copyright (C) 2001 Python Software Foundation, www.python.org
99 99 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
100 100 #
101 101 #
102 102 #*****************************************************************************
103 103
104 104 import inspect
105 105 import linecache
106 106 import sys
107 107 import re
108 108 import os
109 109
110 110 from IPython import get_ipython
111 111 from IPython.utils import PyColorize
112 112 from IPython.utils import coloransi, py3compat
113 113 from IPython.core.excolors import exception_colors
114 114
115 115 # skip module docstests
116 116 __skip_doctest__ = True
117 117
118 118 prompt = 'ipdb> '
119 119
120 120 # We have to check this directly from sys.argv, config struct not yet available
121 121 from pdb import Pdb as OldPdb
122 122
123 123 # Allow the set_trace code to operate outside of an ipython instance, even if
124 124 # it does so with some limitations. The rest of this support is implemented in
125 125 # the Tracer constructor.
126 126
127 127 DEBUGGERSKIP = "__debuggerskip__"
128 128
129 129
130 130 def make_arrow(pad):
131 131 """generate the leading arrow in front of traceback or debugger"""
132 132 if pad >= 2:
133 133 return '-'*(pad-2) + '> '
134 134 elif pad == 1:
135 135 return '>'
136 136 return ''
137 137
138 138
139 139 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
140 140 """Exception hook which handles `BdbQuit` exceptions.
141 141
142 142 All other exceptions are processed using the `excepthook`
143 143 parameter.
144 144 """
145 145 raise ValueError(
146 146 "`BdbQuit_excepthook` is deprecated since version 5.1",
147 147 )
148 148
149 149
150 150 def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None):
151 151 raise ValueError(
152 152 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
153 153 DeprecationWarning, stacklevel=2)
154 154
155 155
156 156 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
157 157
158 158
159 159 def strip_indentation(multiline_string):
160 160 return RGX_EXTRA_INDENT.sub('', multiline_string)
161 161
162 162
163 163 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
164 164 """Make new_fn have old_fn's doc string. This is particularly useful
165 165 for the ``do_...`` commands that hook into the help system.
166 166 Adapted from from a comp.lang.python posting
167 167 by Duncan Booth."""
168 168 def wrapper(*args, **kw):
169 169 return new_fn(*args, **kw)
170 170 if old_fn.__doc__:
171 171 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
172 172 return wrapper
173 173
174 174
175 175 class Pdb(OldPdb):
176 176 """Modified Pdb class, does not load readline.
177 177
178 178 for a standalone version that uses prompt_toolkit, see
179 179 `IPython.terminal.debugger.TerminalPdb` and
180 180 `IPython.terminal.debugger.set_trace()`
181 181
182 182
183 183 This debugger can hide and skip frames that are tagged according to some predicates.
184 184 See the `skip_predicates` commands.
185 185
186 186 """
187 187
188 188 default_predicates = {
189 189 "tbhide": True,
190 190 "readonly": False,
191 191 "ipython_internal": True,
192 192 "debuggerskip": True,
193 193 }
194 194
195 195 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
196 196 """Create a new IPython debugger.
197 197
198 198 Parameters
199 199 ----------
200 200 completekey : default None
201 201 Passed to pdb.Pdb.
202 202 stdin : default None
203 203 Passed to pdb.Pdb.
204 204 stdout : default None
205 205 Passed to pdb.Pdb.
206 206 context : int
207 207 Number of lines of source code context to show when
208 208 displaying stacktrace information.
209 209 **kwargs
210 210 Passed to pdb.Pdb.
211 211
212 212 Notes
213 213 -----
214 214 The possibilities are python version dependent, see the python
215 215 docs for more info.
216 216 """
217 217
218 218 # Parent constructor:
219 219 try:
220 220 self.context = int(context)
221 221 if self.context <= 0:
222 222 raise ValueError("Context must be a positive integer")
223 223 except (TypeError, ValueError) as e:
224 224 raise ValueError("Context must be a positive integer") from e
225 225
226 226 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
227 227 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
228 228
229 229 # IPython changes...
230 230 self.shell = get_ipython()
231 231
232 232 if self.shell is None:
233 233 save_main = sys.modules['__main__']
234 234 # No IPython instance running, we must create one
235 235 from IPython.terminal.interactiveshell import \
236 236 TerminalInteractiveShell
237 237 self.shell = TerminalInteractiveShell.instance()
238 238 # needed by any code which calls __import__("__main__") after
239 239 # the debugger was entered. See also #9941.
240 240 sys.modules["__main__"] = save_main
241 241
242 242
243 243 color_scheme = self.shell.colors
244 244
245 245 self.aliases = {}
246 246
247 247 # Create color table: we copy the default one from the traceback
248 248 # module and add a few attributes needed for debugging
249 249 self.color_scheme_table = exception_colors()
250 250
251 251 # shorthands
252 252 C = coloransi.TermColors
253 253 cst = self.color_scheme_table
254 254
255 255 cst['NoColor'].colors.prompt = C.NoColor
256 256 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
257 257 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
258 258
259 259 cst['Linux'].colors.prompt = C.Green
260 260 cst['Linux'].colors.breakpoint_enabled = C.LightRed
261 261 cst['Linux'].colors.breakpoint_disabled = C.Red
262 262
263 263 cst['LightBG'].colors.prompt = C.Blue
264 264 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
265 265 cst['LightBG'].colors.breakpoint_disabled = C.Red
266 266
267 267 cst['Neutral'].colors.prompt = C.Blue
268 268 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
269 269 cst['Neutral'].colors.breakpoint_disabled = C.Red
270 270
271 271 # Add a python parser so we can syntax highlight source while
272 272 # debugging.
273 273 self.parser = PyColorize.Parser(style=color_scheme)
274 274 self.set_colors(color_scheme)
275 275
276 276 # Set the prompt - the default prompt is '(Pdb)'
277 277 self.prompt = prompt
278 278 self.skip_hidden = True
279 279 self.report_skipped = True
280 280
281 281 # list of predicates we use to skip frames
282 282 self._predicates = self.default_predicates
283 283
284 284 #
285 285 def set_colors(self, scheme):
286 286 """Shorthand access to the color table scheme selector method."""
287 287 self.color_scheme_table.set_active_scheme(scheme)
288 288 self.parser.style = scheme
289 289
290 290 def set_trace(self, frame=None):
291 291 if frame is None:
292 292 frame = sys._getframe().f_back
293 293 self.initial_frame = frame
294 294 return super().set_trace(frame)
295 295
296 296 def _hidden_predicate(self, frame):
297 297 """
298 298 Given a frame return whether it it should be hidden or not by IPython.
299 299 """
300 300
301 301 if self._predicates["readonly"]:
302 302 fname = frame.f_code.co_filename
303 303 # we need to check for file existence and interactively define
304 304 # function would otherwise appear as RO.
305 305 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
306 306 return True
307 307
308 308 if self._predicates["tbhide"]:
309 309 if frame in (self.curframe, getattr(self, "initial_frame", None)):
310 310 return False
311 311 frame_locals = self._get_frame_locals(frame)
312 312 if "__tracebackhide__" not in frame_locals:
313 313 return False
314 314 return frame_locals["__tracebackhide__"]
315 315 return False
316 316
317 317 def hidden_frames(self, stack):
318 318 """
319 319 Given an index in the stack return whether it should be skipped.
320 320
321 321 This is used in up/down and where to skip frames.
322 322 """
323 323 # The f_locals dictionary is updated from the actual frame
324 324 # locals whenever the .f_locals accessor is called, so we
325 325 # avoid calling it here to preserve self.curframe_locals.
326 326 # Furthermore, there is no good reason to hide the current frame.
327 327 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
328 328 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
329 329 if ip_start and self._predicates["ipython_internal"]:
330 330 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
331 331 return ip_hide
332 332
333 333 def interaction(self, frame, traceback):
334 334 try:
335 335 OldPdb.interaction(self, frame, traceback)
336 336 except KeyboardInterrupt:
337 337 self.stdout.write("\n" + self.shell.get_exception_only())
338 338
339 339 def precmd(self, line):
340 340 """Perform useful escapes on the command before it is executed."""
341 341
342 342 if line.endswith("??"):
343 343 line = "pinfo2 " + line[:-2]
344 344 elif line.endswith("?"):
345 345 line = "pinfo " + line[:-1]
346 346
347 347 line = super().precmd(line)
348 348
349 349 return line
350 350
351 351 def new_do_frame(self, arg):
352 352 OldPdb.do_frame(self, arg)
353 353
354 354 def new_do_quit(self, arg):
355 355
356 356 if hasattr(self, 'old_all_completions'):
357 357 self.shell.Completer.all_completions = self.old_all_completions
358 358
359 359 return OldPdb.do_quit(self, arg)
360 360
361 361 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
362 362
363 363 def new_do_restart(self, arg):
364 364 """Restart command. In the context of ipython this is exactly the same
365 365 thing as 'quit'."""
366 366 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
367 367 return self.do_quit(arg)
368 368
369 369 def print_stack_trace(self, context=None):
370 370 Colors = self.color_scheme_table.active_colors
371 371 ColorsNormal = Colors.Normal
372 372 if context is None:
373 373 context = self.context
374 374 try:
375 375 context = int(context)
376 376 if context <= 0:
377 377 raise ValueError("Context must be a positive integer")
378 378 except (TypeError, ValueError) as e:
379 379 raise ValueError("Context must be a positive integer") from e
380 380 try:
381 381 skipped = 0
382 382 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
383 383 if hidden and self.skip_hidden:
384 384 skipped += 1
385 385 continue
386 386 if skipped:
387 387 print(
388 388 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
389 389 )
390 390 skipped = 0
391 391 self.print_stack_entry(frame_lineno, context=context)
392 392 if skipped:
393 393 print(
394 394 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
395 395 )
396 396 except KeyboardInterrupt:
397 397 pass
398 398
399 399 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
400 400 context=None):
401 401 if context is None:
402 402 context = self.context
403 403 try:
404 404 context = int(context)
405 405 if context <= 0:
406 406 raise ValueError("Context must be a positive integer")
407 407 except (TypeError, ValueError) as e:
408 408 raise ValueError("Context must be a positive integer") from e
409 409 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
410 410
411 411 # vds: >>
412 412 frame, lineno = frame_lineno
413 413 filename = frame.f_code.co_filename
414 414 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
415 415 # vds: <<
416 416
417 417 def _get_frame_locals(self, frame):
418 418 """ "
419 419 Accessing f_local of current frame reset the namespace, so we want to avoid
420 420 that or the following can happen
421 421
422 422 ipdb> foo
423 423 "old"
424 424 ipdb> foo = "new"
425 425 ipdb> foo
426 426 "new"
427 427 ipdb> where
428 428 ipdb> foo
429 429 "old"
430 430
431 431 So if frame is self.current_frame we instead return self.curframe_locals
432 432
433 433 """
434 434 if frame is self.curframe:
435 435 return self.curframe_locals
436 436 else:
437 437 return frame.f_locals
438 438
439 439 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
440 440 if context is None:
441 441 context = self.context
442 442 try:
443 443 context = int(context)
444 444 if context <= 0:
445 445 print("Context must be a positive integer", file=self.stdout)
446 446 except (TypeError, ValueError):
447 447 print("Context must be a positive integer", file=self.stdout)
448 448
449 449 import reprlib
450 450
451 451 ret = []
452 452
453 453 Colors = self.color_scheme_table.active_colors
454 454 ColorsNormal = Colors.Normal
455 455 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
456 456 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
457 457 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
458 458 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
459 459
460 460 frame, lineno = frame_lineno
461 461
462 462 return_value = ''
463 463 loc_frame = self._get_frame_locals(frame)
464 464 if "__return__" in loc_frame:
465 465 rv = loc_frame["__return__"]
466 466 # return_value += '->'
467 467 return_value += reprlib.repr(rv) + "\n"
468 468 ret.append(return_value)
469 469
470 470 #s = filename + '(' + `lineno` + ')'
471 471 filename = self.canonic(frame.f_code.co_filename)
472 472 link = tpl_link % py3compat.cast_unicode(filename)
473 473
474 474 if frame.f_code.co_name:
475 475 func = frame.f_code.co_name
476 476 else:
477 477 func = "<lambda>"
478 478
479 479 call = ""
480 480 if func != "?":
481 481 if "__args__" in loc_frame:
482 482 args = reprlib.repr(loc_frame["__args__"])
483 483 else:
484 484 args = '()'
485 485 call = tpl_call % (func, args)
486 486
487 487 # The level info should be generated in the same format pdb uses, to
488 488 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
489 489 if frame is self.curframe:
490 490 ret.append('> ')
491 491 else:
492 492 ret.append(" ")
493 493 ret.append("%s(%s)%s\n" % (link, lineno, call))
494 494
495 495 start = lineno - 1 - context//2
496 496 lines = linecache.getlines(filename)
497 497 start = min(start, len(lines) - context)
498 498 start = max(start, 0)
499 499 lines = lines[start : start + context]
500 500
501 501 for i, line in enumerate(lines):
502 502 show_arrow = start + 1 + i == lineno
503 503 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
504 504 ret.append(
505 505 self.__format_line(
506 506 linetpl, filename, start + 1 + i, line, arrow=show_arrow
507 507 )
508 508 )
509 509 return "".join(ret)
510 510
511 511 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
512 512 bp_mark = ""
513 513 bp_mark_color = ""
514 514
515 515 new_line, err = self.parser.format2(line, 'str')
516 516 if not err:
517 517 line = new_line
518 518
519 519 bp = None
520 520 if lineno in self.get_file_breaks(filename):
521 521 bps = self.get_breaks(filename, lineno)
522 522 bp = bps[-1]
523 523
524 524 if bp:
525 525 Colors = self.color_scheme_table.active_colors
526 526 bp_mark = str(bp.number)
527 527 bp_mark_color = Colors.breakpoint_enabled
528 528 if not bp.enabled:
529 529 bp_mark_color = Colors.breakpoint_disabled
530 530
531 531 numbers_width = 7
532 532 if arrow:
533 533 # This is the line with the error
534 534 pad = numbers_width - len(str(lineno)) - len(bp_mark)
535 535 num = '%s%s' % (make_arrow(pad), str(lineno))
536 536 else:
537 537 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
538 538
539 539 return tpl_line % (bp_mark_color + bp_mark, num, line)
540 540
541 541 def print_list_lines(self, filename, first, last):
542 542 """The printing (as opposed to the parsing part of a 'list'
543 543 command."""
544 544 try:
545 545 Colors = self.color_scheme_table.active_colors
546 546 ColorsNormal = Colors.Normal
547 547 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
548 548 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
549 549 src = []
550 550 if filename == "<string>" and hasattr(self, "_exec_filename"):
551 551 filename = self._exec_filename
552 552
553 553 for lineno in range(first, last+1):
554 554 line = linecache.getline(filename, lineno)
555 555 if not line:
556 556 break
557 557
558 558 if lineno == self.curframe.f_lineno:
559 559 line = self.__format_line(
560 560 tpl_line_em, filename, lineno, line, arrow=True
561 561 )
562 562 else:
563 563 line = self.__format_line(
564 564 tpl_line, filename, lineno, line, arrow=False
565 565 )
566 566
567 567 src.append(line)
568 568 self.lineno = lineno
569 569
570 570 print(''.join(src), file=self.stdout)
571 571
572 572 except KeyboardInterrupt:
573 573 pass
574 574
575 575 def do_skip_predicates(self, args):
576 576 """
577 577 Turn on/off individual predicates as to whether a frame should be hidden/skip.
578 578
579 579 The global option to skip (or not) hidden frames is set with skip_hidden
580 580
581 581 To change the value of a predicate
582 582
583 583 skip_predicates key [true|false]
584 584
585 585 Call without arguments to see the current values.
586 586
587 587 To permanently change the value of an option add the corresponding
588 588 command to your ``~/.pdbrc`` file. If you are programmatically using the
589 589 Pdb instance you can also change the ``default_predicates`` class
590 590 attribute.
591 591 """
592 592 if not args.strip():
593 593 print("current predicates:")
594 594 for p, v in self._predicates.items():
595 595 print(" ", p, ":", v)
596 596 return
597 597 type_value = args.strip().split(" ")
598 598 if len(type_value) != 2:
599 599 print(
600 600 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
601 601 )
602 602 return
603 603
604 604 type_, value = type_value
605 605 if type_ not in self._predicates:
606 606 print(f"{type_!r} not in {set(self._predicates.keys())}")
607 607 return
608 608 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
609 609 print(
610 610 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
611 611 )
612 612 return
613 613
614 614 self._predicates[type_] = value.lower() in ("true", "yes", "1")
615 615 if not any(self._predicates.values()):
616 616 print(
617 617 "Warning, all predicates set to False, skip_hidden may not have any effects."
618 618 )
619 619
620 620 def do_skip_hidden(self, arg):
621 621 """
622 622 Change whether or not we should skip frames with the
623 623 __tracebackhide__ attribute.
624 624 """
625 625 if not arg.strip():
626 626 print(
627 627 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
628 628 )
629 629 elif arg.strip().lower() in ("true", "yes"):
630 630 self.skip_hidden = True
631 631 elif arg.strip().lower() in ("false", "no"):
632 632 self.skip_hidden = False
633 633 if not any(self._predicates.values()):
634 634 print(
635 635 "Warning, all predicates set to False, skip_hidden may not have any effects."
636 636 )
637 637
638 638 def do_list(self, arg):
639 639 """Print lines of code from the current stack frame
640 640 """
641 641 self.lastcmd = 'list'
642 642 last = None
643 if arg:
643 if arg and arg != ".":
644 644 try:
645 645 x = eval(arg, {}, {})
646 646 if type(x) == type(()):
647 647 first, last = x
648 648 first = int(first)
649 649 last = int(last)
650 650 if last < first:
651 651 # Assume it's a count
652 652 last = first + last
653 653 else:
654 654 first = max(1, int(x) - 5)
655 655 except:
656 656 print('*** Error in argument:', repr(arg), file=self.stdout)
657 657 return
658 elif self.lineno is None:
658 elif self.lineno is None or arg == ".":
659 659 first = max(1, self.curframe.f_lineno - 5)
660 660 else:
661 661 first = self.lineno + 1
662 662 if last is None:
663 663 last = first + 10
664 664 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
665 665
666 666 # vds: >>
667 667 lineno = first
668 668 filename = self.curframe.f_code.co_filename
669 669 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
670 670 # vds: <<
671 671
672 672 do_l = do_list
673 673
674 674 def getsourcelines(self, obj):
675 675 lines, lineno = inspect.findsource(obj)
676 676 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
677 677 # must be a module frame: do not try to cut a block out of it
678 678 return lines, 1
679 679 elif inspect.ismodule(obj):
680 680 return lines, 1
681 681 return inspect.getblock(lines[lineno:]), lineno+1
682 682
683 683 def do_longlist(self, arg):
684 684 """Print lines of code from the current stack frame.
685 685
686 686 Shows more lines than 'list' does.
687 687 """
688 688 self.lastcmd = 'longlist'
689 689 try:
690 690 lines, lineno = self.getsourcelines(self.curframe)
691 691 except OSError as err:
692 692 self.error(err)
693 693 return
694 694 last = lineno + len(lines)
695 695 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
696 696 do_ll = do_longlist
697 697
698 698 def do_debug(self, arg):
699 699 """debug code
700 700 Enter a recursive debugger that steps through the code
701 701 argument (which is an arbitrary expression or statement to be
702 702 executed in the current environment).
703 703 """
704 704 trace_function = sys.gettrace()
705 705 sys.settrace(None)
706 706 globals = self.curframe.f_globals
707 707 locals = self.curframe_locals
708 708 p = self.__class__(completekey=self.completekey,
709 709 stdin=self.stdin, stdout=self.stdout)
710 710 p.use_rawinput = self.use_rawinput
711 711 p.prompt = "(%s) " % self.prompt.strip()
712 712 self.message("ENTERING RECURSIVE DEBUGGER")
713 713 sys.call_tracing(p.run, (arg, globals, locals))
714 714 self.message("LEAVING RECURSIVE DEBUGGER")
715 715 sys.settrace(trace_function)
716 716 self.lastcmd = p.lastcmd
717 717
718 718 def do_pdef(self, arg):
719 719 """Print the call signature for any callable object.
720 720
721 721 The debugger interface to %pdef"""
722 722 namespaces = [
723 723 ("Locals", self.curframe_locals),
724 724 ("Globals", self.curframe.f_globals),
725 725 ]
726 726 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
727 727
728 728 def do_pdoc(self, arg):
729 729 """Print the docstring for an object.
730 730
731 731 The debugger interface to %pdoc."""
732 732 namespaces = [
733 733 ("Locals", self.curframe_locals),
734 734 ("Globals", self.curframe.f_globals),
735 735 ]
736 736 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
737 737
738 738 def do_pfile(self, arg):
739 739 """Print (or run through pager) the file where an object is defined.
740 740
741 741 The debugger interface to %pfile.
742 742 """
743 743 namespaces = [
744 744 ("Locals", self.curframe_locals),
745 745 ("Globals", self.curframe.f_globals),
746 746 ]
747 747 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
748 748
749 749 def do_pinfo(self, arg):
750 750 """Provide detailed information about an object.
751 751
752 752 The debugger interface to %pinfo, i.e., obj?."""
753 753 namespaces = [
754 754 ("Locals", self.curframe_locals),
755 755 ("Globals", self.curframe.f_globals),
756 756 ]
757 757 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
758 758
759 759 def do_pinfo2(self, arg):
760 760 """Provide extra detailed information about an object.
761 761
762 762 The debugger interface to %pinfo2, i.e., obj??."""
763 763 namespaces = [
764 764 ("Locals", self.curframe_locals),
765 765 ("Globals", self.curframe.f_globals),
766 766 ]
767 767 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
768 768
769 769 def do_psource(self, arg):
770 770 """Print (or run through pager) the source code for an object."""
771 771 namespaces = [
772 772 ("Locals", self.curframe_locals),
773 773 ("Globals", self.curframe.f_globals),
774 774 ]
775 775 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
776 776
777 777 def do_where(self, arg):
778 778 """w(here)
779 779 Print a stack trace, with the most recent frame at the bottom.
780 780 An arrow indicates the "current frame", which determines the
781 781 context of most commands. 'bt' is an alias for this command.
782 782
783 783 Take a number as argument as an (optional) number of context line to
784 784 print"""
785 785 if arg:
786 786 try:
787 787 context = int(arg)
788 788 except ValueError as err:
789 789 self.error(err)
790 790 return
791 791 self.print_stack_trace(context)
792 792 else:
793 793 self.print_stack_trace()
794 794
795 795 do_w = do_where
796 796
797 797 def break_anywhere(self, frame):
798 798 """
799 799 _stop_in_decorator_internals is overly restrictive, as we may still want
800 800 to trace function calls, so we need to also update break_anywhere so
801 801 that is we don't `stop_here`, because of debugger skip, we may still
802 802 stop at any point inside the function
803 803
804 804 """
805 805
806 806 sup = super().break_anywhere(frame)
807 807 if sup:
808 808 return sup
809 809 if self._predicates["debuggerskip"]:
810 810 if DEBUGGERSKIP in frame.f_code.co_varnames:
811 811 return True
812 812 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
813 813 return True
814 814 return False
815 815
816 816 def _is_in_decorator_internal_and_should_skip(self, frame):
817 817 """
818 818 Utility to tell us whether we are in a decorator internal and should stop.
819 819
820 820 """
821 821
822 822 # if we are disabled don't skip
823 823 if not self._predicates["debuggerskip"]:
824 824 return False
825 825
826 826 # if frame is tagged, skip by default.
827 827 if DEBUGGERSKIP in frame.f_code.co_varnames:
828 828 return True
829 829
830 830 # if one of the parent frame value set to True skip as well.
831 831
832 832 cframe = frame
833 833 while getattr(cframe, "f_back", None):
834 834 cframe = cframe.f_back
835 835 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
836 836 return True
837 837
838 838 return False
839 839
840 840 def stop_here(self, frame):
841 841 if self._is_in_decorator_internal_and_should_skip(frame) is True:
842 842 return False
843 843
844 844 hidden = False
845 845 if self.skip_hidden:
846 846 hidden = self._hidden_predicate(frame)
847 847 if hidden:
848 848 if self.report_skipped:
849 849 Colors = self.color_scheme_table.active_colors
850 850 ColorsNormal = Colors.Normal
851 851 print(
852 852 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
853 853 )
854 854 return super().stop_here(frame)
855 855
856 856 def do_up(self, arg):
857 857 """u(p) [count]
858 858 Move the current frame count (default one) levels up in the
859 859 stack trace (to an older frame).
860 860
861 861 Will skip hidden frames.
862 862 """
863 863 # modified version of upstream that skips
864 864 # frames with __tracebackhide__
865 865 if self.curindex == 0:
866 866 self.error("Oldest frame")
867 867 return
868 868 try:
869 869 count = int(arg or 1)
870 870 except ValueError:
871 871 self.error("Invalid frame count (%s)" % arg)
872 872 return
873 873 skipped = 0
874 874 if count < 0:
875 875 _newframe = 0
876 876 else:
877 877 counter = 0
878 878 hidden_frames = self.hidden_frames(self.stack)
879 879 for i in range(self.curindex - 1, -1, -1):
880 880 if hidden_frames[i] and self.skip_hidden:
881 881 skipped += 1
882 882 continue
883 883 counter += 1
884 884 if counter >= count:
885 885 break
886 886 else:
887 887 # if no break occurred.
888 888 self.error(
889 889 "all frames above hidden, use `skip_hidden False` to get get into those."
890 890 )
891 891 return
892 892
893 893 Colors = self.color_scheme_table.active_colors
894 894 ColorsNormal = Colors.Normal
895 895 _newframe = i
896 896 self._select_frame(_newframe)
897 897 if skipped:
898 898 print(
899 899 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
900 900 )
901 901
902 902 def do_down(self, arg):
903 903 """d(own) [count]
904 904 Move the current frame count (default one) levels down in the
905 905 stack trace (to a newer frame).
906 906
907 907 Will skip hidden frames.
908 908 """
909 909 if self.curindex + 1 == len(self.stack):
910 910 self.error("Newest frame")
911 911 return
912 912 try:
913 913 count = int(arg or 1)
914 914 except ValueError:
915 915 self.error("Invalid frame count (%s)" % arg)
916 916 return
917 917 if count < 0:
918 918 _newframe = len(self.stack) - 1
919 919 else:
920 920 counter = 0
921 921 skipped = 0
922 922 hidden_frames = self.hidden_frames(self.stack)
923 923 for i in range(self.curindex + 1, len(self.stack)):
924 924 if hidden_frames[i] and self.skip_hidden:
925 925 skipped += 1
926 926 continue
927 927 counter += 1
928 928 if counter >= count:
929 929 break
930 930 else:
931 931 self.error(
932 932 "all frames below hidden, use `skip_hidden False` to get get into those."
933 933 )
934 934 return
935 935
936 936 Colors = self.color_scheme_table.active_colors
937 937 ColorsNormal = Colors.Normal
938 938 if skipped:
939 939 print(
940 940 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
941 941 )
942 942 _newframe = i
943 943
944 944 self._select_frame(_newframe)
945 945
946 946 do_d = do_down
947 947 do_u = do_up
948 948
949 949 def do_context(self, context):
950 950 """context number_of_lines
951 951 Set the number of lines of source code to show when displaying
952 952 stacktrace information.
953 953 """
954 954 try:
955 955 new_context = int(context)
956 956 if new_context <= 0:
957 957 raise ValueError()
958 958 self.context = new_context
959 959 except ValueError:
960 960 self.error("The 'context' command requires a positive integer argument.")
961 961
962 962
963 963 class InterruptiblePdb(Pdb):
964 964 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
965 965
966 966 def cmdloop(self, intro=None):
967 967 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
968 968 try:
969 969 return OldPdb.cmdloop(self, intro=intro)
970 970 except KeyboardInterrupt:
971 971 self.stop_here = lambda frame: False
972 972 self.do_quit("")
973 973 sys.settrace(None)
974 974 self.quitting = False
975 975 raise
976 976
977 977 def _cmdloop(self):
978 978 while True:
979 979 try:
980 980 # keyboard interrupts allow for an easy way to cancel
981 981 # the current command, so allow them during interactive input
982 982 self.allow_kbdint = True
983 983 self.cmdloop()
984 984 self.allow_kbdint = False
985 985 break
986 986 except KeyboardInterrupt:
987 987 self.message('--KeyboardInterrupt--')
988 988 raise
989 989
990 990
991 991 def set_trace(frame=None):
992 992 """
993 993 Start debugging from `frame`.
994 994
995 995 If frame is not specified, debugging starts from caller's frame.
996 996 """
997 997 Pdb().set_trace(frame or sys._getframe().f_back)
General Comments 0
You need to be logged in to leave comments. Login now