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