示例#1
0
def test_color_for_obj(monkeypatch):
    class Config(ColorConfig):
        color_by_type = {}

    compl = Completer({}, Config)
    assert compl.color_for_obj(1, "foo",
                               "bar") == "\x1b[001;00m\x1b[00mfoo\x1b[00m"
示例#2
0
def test_complete_attribute():
    compl = Completer({'a': None}, ConfigForTest)
    assert compl.attr_matches('a.') == ['a.__']
    matches = compl.attr_matches('a.__')
    assert 'a.__class__' not in matches
    assert '__class__' in matches
    assert compl.attr_matches('a.__class') == ['a.__class__']
示例#3
0
def test_complete_with_indexer():
    compl = Completer({'lst': [None, 2, 3]}, ConfigForTest)
    assert compl.attr_matches('lst[0].') == ['lst[0].__']
    matches = compl.attr_matches('lst[0].__')
    assert 'lst[0].__class__' not in matches
    assert '__class__' in matches
    assert compl.attr_matches('lst[0].__class') == ['lst[0].__class__']
示例#4
0
def test_does_not_color_single_match():
    class obj:
        msgs = []

    compl = Completer({'obj': obj}, ColorConfig)
    matches = compl.attr_matches('obj.msgs')
    assert matches == ['obj.msgs']
示例#5
0
def test_autocomplete():
    class A:
        aaa = None
        abc_1 = None
        abc_2 = None
        abc_3 = None
        bbb = None

    compl = Completer({'A': A}, ConfigForTest)
    #
    # in this case, we want to display all attributes which start with
    # 'a'. MOREOVER, we also include a space to prevent readline to
    # automatically insert the common prefix (which will the the ANSI escape
    # sequence if we use colors)
    matches = compl.attr_matches('A.a')
    assert sorted(matches) == [' ', 'aaa', 'abc_1', 'abc_2', 'abc_3']
    #
    # IF there is an actual common prefix, we return just it, so that readline
    # will insert it into place
    matches = compl.attr_matches('A.ab')
    assert matches == ['A.abc_']
    #
    # finally, at the next TAB, we display again all the completions available
    # for this common prefix. Agai, we insert a spurious space to prevent the
    # automatic completion of ANSI sequences
    matches = compl.attr_matches('A.abc_')
    assert sorted(matches) == [' ', 'abc_1', 'abc_2', 'abc_3']
示例#6
0
def test_unicode_in___dir__():
    class Foo(object):
        def __dir__(self):
            return [u'hello', 'world']

    compl = Completer({'a': Foo()}, ConfigForTest)
    matches = compl.attr_matches('a.')
    assert matches == ['hello', 'world']
    assert type(matches[0]) is str
示例#7
0
def test_complete_attribute_colored():
    compl = Completer({'a': 42}, ColorConfig)
    matches = compl.attr_matches('a.__')
    assert len(matches) > 2
    expected_part = Color.set('31', '__class__')
    for match in matches:
        if expected_part in match:
            break
    else:
        assert False
    assert ' ' in matches
示例#8
0
def test_complete_global_colored_exception():
    compl = Completer({'tryme': ValueError()}, ColorConfig)
    if sys.version_info >= (3, 6):
        assert compl.global_matches('try') == [
            '\x1b[000;00m\x1b[37mtry:\x1b[00m',
            '\x1b[001;00m\x1b[31;01mtryme\x1b[00m', ' '
        ]
    else:
        assert compl.global_matches('try') == [
            '\x1b[000;00m\x1b[37mtry\x1b[00m',
            '\x1b[001;00m\x1b[31;01mtryme\x1b[00m', ' '
        ]
示例#9
0
def test_complete_attribute_colored():
    compl = Completer({'a': 42}, ColorConfig)
    matches = compl.attr_matches('a.__')
    assert len(matches) > 2
    expected_color = compl.config.color_by_type.get(type(compl.__class__))
    assert expected_color == '35;01'
    expected_part = Color.set(expected_color, '__class__')
    for match in matches:
        if expected_part in match:
            break
    else:
        assert False, matches
    assert ' ' in matches
示例#10
0
def test_complete_attribute_colored():
    class ColorConfig(DefaultConfig):
        use_colors = True
        color_by_type = {type: '31'}

    compl = Completer({'a': 42}, ColorConfig)
    matches = compl.attr_matches('a.__')
    for match in matches:
        if Color.set('31', '__class__') in match:
            break
    else:
        assert False
    assert ' ' in matches
示例#11
0
def test_complete_global_exception(monkeypatch):
    import rlcompleter

    def rlcompleter_global_matches(self, text):
        return ['trigger_exception!', 'nameerror', 'valid']

    monkeypatch.setattr(rlcompleter.Completer, 'global_matches',
                        rlcompleter_global_matches)

    compl = Completer({'valid': 42}, ColorConfig)
    assert compl.global_matches("") == [
        "\x1b[000;00m\x1b[31;01mnameerror\x1b[00m",
        "\x1b[001;00m\x1b[31;01mtrigger_exception!\x1b[00m",
        "\x1b[002;00m\x1b[33;01mvalid\x1b[00m",
        " ",
    ]
示例#12
0
    def complete(self, text, state):
        """Handle completions from fancycompleter and original pdb."""
        if state == 0:
            if GLOBAL_PDB:
                GLOBAL_PDB._pdbpp_completing = True
            mydict = self.curframe.f_globals.copy()
            mydict.update(self.curframe.f_locals)
            completer = Completer(mydict)
            self._completions = self._get_all_completions(
                completer.complete, text)

            real_pdb = super(Pdb, self)
            for x in self._get_all_completions(real_pdb.complete, text):
                if x not in self._completions:
                    self._completions.append(x)

            if GLOBAL_PDB:
                del GLOBAL_PDB._pdbpp_completing

            # Remove "\t" from fancycompleter if there are pdb completions.
            if len(self._completions) > 1 and self._completions[0] == "\t":
                self._completions.pop(0)

        try:
            return self._completions[state]
        except IndexError:
            return None
示例#13
0
def test_complete_attribute_prefix():
    class C(object):
        attr = 1
        _attr = 2
        __attr__attr = 3
    compl = Completer({'a': C}, ConfigForTest)
    assert compl.attr_matches('a.') == ['attr', 'mro']
    assert compl.attr_matches('a._') == ['_C__attr__attr', '_attr']
    matches = compl.attr_matches('a.__')
    assert 'a.__class__' not in matches
    assert '__class__' in matches
    assert compl.attr_matches('a.__class') == ['a.__class__']

    compl = Completer({'a': None}, ConfigForTest)
    assert compl.attr_matches('a._') == ['a.__']
示例#14
0
def test_complete_global():
    compl = Completer({'foobar': 1, 'foobazzz': 2}, ConfigForTest)
    assert compl.global_matches('foo') == ['fooba']
    matches = compl.global_matches('fooba')
    assert set(matches) == set(['foobar', 'foobazzz'])
    assert compl.global_matches('foobaz') == ['foobazzz']
    assert compl.global_matches('nothing') == []
示例#15
0
def test_complete_global_colored():
    compl = Completer({'foobar': 1, 'foobazzz': 2}, ColorConfig)
    assert compl.global_matches('foo') == ['fooba']
    matches = compl.global_matches('fooba')
    assert set(matches) == {
        ' ',
        '\x1b[001;00m\x1b[33;01mfoobazzz\x1b[00m',
        '\x1b[000;00m\x1b[33;01mfoobar\x1b[00m',
    }
    assert compl.global_matches('foobaz') == ['foobazzz']
    assert compl.global_matches('nothing') == []
示例#16
0
def test_complete_colored_single_match():
    """No coloring, via commonprefix."""
    compl = Completer({'foobar': 42}, ColorConfig)
    matches = compl.global_matches('foob')
    assert matches == ['foobar']
示例#17
0
 def complete(self, text, state):
     if state == 0:
         mydict = self.curframe.f_globals.copy()
         mydict.update(self.curframe.f_locals)
         self.mycompleter = Completer(mydict)
     return self.mycompleter.complete(text, state)
示例#18
0
文件: pdb.py 项目: holycrepe/pdbpp
 def complete(self, text, state):
     if state == 0:
         mydict = self.curframe.f_globals.copy()
         mydict.update(self.curframe.f_locals)
         self.mycompleter = Completer(mydict)
     return self.mycompleter.complete(text, state)
示例#19
0
def test_complete_function_skipped():
    compl = Completer({'str': str}, ConfigForTest)
    assert compl.attr_matches('str.split().') == []
示例#20
0
class Pdb(pdb.Pdb, ConfigurableClass):

    DefaultConfig = DefaultConfig
    config_filename = '.pdbrc.py'

    def __init__(self, *args, **kwds):
        self.ConfigFactory = kwds.pop('Config', None)
        self.start_lineno = kwds.pop('start_lineno', None)
        self.start_filename = kwds.pop('start_filename', None)
        self.config = self.get_config(self.ConfigFactory)
        self.config.setup(self)
        if self.config.disable_pytest_capturing:
            self._disable_pytest_capture_maybe()
        pdb.Pdb.__init__(self, *args, **kwds)
        self.prompt = self.config.prompt

        self.mycompleter = None
        self.display_list = {} # frame --> (name --> last seen value)
        self.sticky = self.config.sticky_by_default
        self.sticky_ranges = {} # frame --> (start, end)
        self.tb_lineno = {} # frame --> lineno where the exception raised
        self.history = []
        self.show_hidden_frames = False
        self.hidden_frames = []
        self.stdout = self.ensure_file_can_write_unicode(self.stdout)

    def ensure_file_can_write_unicode(self, f):
        # Wrap with an encoder, but only if not already wrapped
        if not hasattr(f, 'stream') and \
           getattr(f, 'encoding', False) and f.encoding.lower() != 'utf-8':
            f = codecs.getwriter('utf-8')(f)

        return f

    def _disable_pytest_capture_maybe(self):
        try:
            import py.test
            py.test.config # force to raise ImportError if pytest is not
                           # installed
        except (ImportError, AttributeError):
            return
        try:
            capman = py.test.config.pluginmanager.getplugin('capturemanager')
            capman.suspendcapture()
        except KeyError:
            pass
        except AttributeError:
            pass # newer py.test with support ready, or very old py.test for
                 # which this hack does not work

    def interaction(self, frame, traceback):
        if self.config.exec_if_unfocused:
            self.exec_if_unfocused()
        self.setup(frame, traceback)
        self.print_stack_entry(self.stack[self.curindex])
        self.print_hidden_frames_count()
        completer = fancycompleter.setup()
        old_completer = completer.config.readline.get_completer()
        completer.config.readline.set_completer(self.complete)
        self.config.before_interaction_hook(self)
        self.cmdloop()
        completer.config.readline.set_completer(old_completer)
        self.forget()

    def print_hidden_frames_count(self):
        n = len(self.hidden_frames)
        if n and self.config.show_hidden_frames_count:
            plural = n>1 and 's' or ''
            print("   %d frame%s hidden (try 'help hidden_frames')" % (n, plural),
                  file=self.stdout)

    def exec_if_unfocused(self):
        import os
        import wmctrl
        term = os.getenv('TERM', '')
        try:
            winid = int(os.getenv('WINDOWID'))
        except (TypeError, ValueError):
            return # cannot find WINDOWID of the terminal
        active_win = wmctrl.Window.get_active()
        if not active_win or (int(active_win.id, 16) != winid) and \
           not (active_win.wm_class == 'emacs.Emacs' and term.startswith('eterm')):
            os.system(self.config.exec_if_unfocused)

    def setup(self, frame, tb):
        pdb.Pdb.setup(self, frame, tb)
        while tb:
            lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
            self.tb_lineno[tb.tb_frame] = lineno
            tb = tb.tb_next

    def _is_hidden(self, frame):
        if not self.config.enable_hidden_frames:
            return False
        consts = frame.f_code.co_consts
        if consts and consts[-1] is _HIDE_FRAME:
            return True
        if frame.f_globals.get('__unittest'):
            return True
        if frame.f_locals.get('__tracebackhide__') \
           or frame.f_globals.get('__tracebackhide__'):
            return True

    def get_stack(self, f, t):
        # show all the frames, except the ones that explicitly ask to be hidden
        fullstack, _ = pdb.Pdb.get_stack(self, f, t)
        self.fullstack = fullstack
        return self.compute_stack(fullstack)

    def compute_stack(self, fullstack):
        self.hidden_frames = []
        newstack = []
        for frame, lineno in fullstack:
            if self._is_hidden(frame) and not self.show_hidden_frames:
                self.hidden_frames.append((frame, lineno))
            else:
                newstack.append((frame, lineno))
        stack = newstack
        i = max(0, len(stack) - 1)
        return stack, i

    def refresh_stack(self):
        """
        Recompute the stack after e.g. show_hidden_frames has been modified
        """
        self.stack, _ = self.compute_stack(self.fullstack)
        # find the current frame in the new stack
        for i, (frame, _) in enumerate(self.stack):
            if frame is self.curframe:
                self.curindex = i
                break
        else:
            self.curindex = len(self.stack)-1
            self.curframe = self.stack[-1][0]
            self.print_current_stack_entry()

    def forget(self):
        pdb.Pdb.forget(self)
        self.raise_lineno = {}

    def complete(self, text, state):
        if state == 0:
            mydict = self.curframe.f_globals.copy()
            mydict.update(self.curframe.f_locals)
            self.mycompleter = Completer(mydict)
        return self.mycompleter.complete(text, state)

    def _init_pygments(self):
        if not self.config.use_pygments:
            return False
        try:
            from pygments.lexers import PythonLexer
            from pygments.formatters import TerminalFormatter, Terminal256Formatter
        except ImportError:
            return False

        if hasattr(self, '_fmt'):
            return True
        if hasattr(self.config, 'formatter'):
            self._fmt = self.config.formatter
        else:
            Formatter = (Terminal256Formatter
                         if self.config.use_terminal256formatter
                            and '256color' in os.environ.get('TERM', '')
                         else TerminalFormatter)
            self._fmt = Formatter(bg=self.config.bg,
                                  colorscheme=self.config.colorscheme)
        self._lexer = PythonLexer()
        return True


    stack_entry_regexp = re.compile(r'(.*?)\(([0-9]+?)\)(.*)', re.DOTALL)
    #
    def format_stack_entry(self, frame_lineno, lprefix=': '):
        entry = pdb.Pdb.format_stack_entry(self, frame_lineno, lprefix)
        entry = self.try_to_decode(entry)
        if self.config.highlight:
            match = self.stack_entry_regexp.match(entry)
            if match:
                filename, lineno, other = match.groups()
                filename = Color.set(self.config.filename_color, filename)
                lineno = Color.set(self.config.line_number_color, lineno)
                entry = '%s(%s)%s' % (filename, lineno, other)
        return entry

    def try_to_decode(self, s):
        for encoding in self.config.encodings:
            try:
                return s.decode(encoding)
            except (UnicodeDecodeError, AttributeError):
                pass
        return s

    def format_source(self, src):
        if not self._init_pygments():
            return src
        from pygments import highlight
        src = self.try_to_decode(src)
        return highlight(src, self._lexer, self._fmt)

    def format_line(self, lineno, marker, line):
        lineno = '%4d' % lineno
        if self.config.highlight:
            lineno = Color.set(self.config.line_number_color, lineno)
        line = '%s  %2s %s' % (lineno, marker, line)
        if self.config.highlight and marker == '->':
            line = setbgcolor(line, self.config.current_line_color)
        return line

    def parseline(self, line):
        if line.startswith('!!'):
            # force the "standard" behaviour, i.e. first check for the
            # command, then for the variable name to display
            line = line[2:]
            return pdb.Pdb.parseline(self, line)
        # pdb++ "smart command mode": don't execute commands if a variable
        # with the name exits in the current contex; this prevents pdb to quit
        # if you type e.g. 'r[0]' by mystake.
        cmd, arg, newline = pdb.Pdb.parseline(self, line)

        if arg and arg.endswith('?'):
            if hasattr(self, 'do_' + cmd):
                cmd, arg = ('help', cmd)
            elif arg.endswith('??'):
                arg = cmd + arg.split('?')[0]
                cmd = 'source'
                self.do_inspect(arg)
                self.stdout.write('%-28s\n' % Color.set(Color.red, 'Source:'))
            else:
                arg = cmd + arg.split('?')[0]
                cmd = 'inspect'
                return cmd, arg, newline

        if cmd and hasattr(self, 'do_'+cmd) and (cmd in self.curframe.f_globals or
                                                 cmd in self.curframe.f_locals or
                                                 arg.startswith('=')):
            line = '!' + line
            return pdb.Pdb.parseline(self, line)
        return cmd, arg, newline

    def do_inspect(self, arg):
        obj = self._getval(arg)

        data = OrderedDict()
        data['Type'] = type(obj).__name__
        data['String Form'] = str(obj).strip()
        try:
            data['Length'] = len(obj)
        except TypeError:
            pass
        try:
            data['File'] = inspect.getabsfile(obj)
        except TypeError:
            pass

        if (isinstance(obj, type)
                and hasattr(obj, '__init__')
                and getattr(obj, '__module__') != '__builtin__'):
            # Class - show definition and docstring for constructor
            data['Docstring'] = obj.__doc__
            data['Constructor information'] = ''
            try:
                data[' Definition'] = '%s%s' % (arg, signature(obj))
            except ValueError:
                pass
            data[' Docstring'] = obj.__init__.__doc__
        else:
            try:
                data['Definition'] = '%s%s' % (arg, signature(obj))
            except (TypeError, ValueError):
                pass
            data['Docstring'] = obj.__doc__

        for key, value in data.items():
            formatted_key = Color.set(Color.red, key + ':')
            self.stdout.write('%-28s %s\n' % (formatted_key, value))

    def default(self, line):
        self.history.append(line)
        return pdb.Pdb.default(self, line)

    def do_help(self, arg):
        try:
            return pdb.Pdb.do_help(self, arg)
        except AttributeError:
            print("*** No help for '{command}'".format(command=arg),
                  file=self.stdout)
    do_help.__doc__ = pdb.Pdb.do_help.__doc__

    def help_hidden_frames(self):
        print("""\
Some frames might be marked as "hidden": by default, hidden frames are not
shown in the stack trace, and cannot be reached using ``up`` and ``down``.
You can use ``hf_unhide`` to tell pdb to ignore the hidden status (i.e., to
treat hidden frames as normal ones), and ``hf_hide`` to hide them again.
``hf_list`` prints a list of hidden frames.

Frames can marked as hidden in the following ways:

- by using the @pdb.hideframe function decorator

- by having __tracebackhide__=True in the locals or the globals of the function
  (this hides py.test internal stuff)

- by having __unittest=True in the globals of the function (this hides
  unittest internal stuff)
""", file=self.stdout)

    def do_hf_unhide(self, arg):
        """
        {hf_show}
        unhide hidden frames, i.e. make it possible to ``up`` or ``down``
        there
        """
        self.show_hidden_frames = True
        self.refresh_stack()

    def do_hf_hide(self, arg):
        """
        {hf_hide}
        (re)hide hidden frames, if they have been unhidden by ``hf_unhide``
        """
        self.show_hidden_frames = False
        self.refresh_stack()

    def do_hf_list(self, arg):
        for frame_lineno in self.hidden_frames:
            print(self.format_stack_entry(frame_lineno, pdb.line_prefix),
                  file=self.stdout)

    def do_longlist(self, arg):
        """
        {longlist|ll}
        List source code for the current function.

        Differently than list, the whole function is displayed; the
        current line is marked with '->'.  In case of post-mortem
        debugging, the line which effectively raised the exception is
        marked with '>>'.

        If the 'highlight' config option is set and pygments is
        installed, the source code is colorized.
        """
        self.lastcmd = 'longlist'
        self._printlonglist()

    def _printlonglist(self, linerange=None):
        try:
            if self.curframe.f_code.co_name == '<module>':
                # inspect.getsourcelines is buggy in this case: if we just
                # pass the frame, it returns the source for the first function
                # defined in the module.  Instead, we want the full source
                # code of the module
                lines, _ = inspect.findsource(self.curframe)
                lineno = 1
            else:
                try:
                    lines, lineno = inspect.getsourcelines(self.curframe)
                except Exception as e:
                    print('** Error in inspect.getsourcelines: %s **' % e, file=self.stdout)
                    return
        except IOError as e:
            print('** Error: %s **' % e, file=self.stdout)
            return
        if linerange:
            start, end = linerange
            start = max(start, lineno)
            end = min(end, lineno+len(lines))
            lines = lines[start-lineno:end-lineno]
            lineno = start
        self._print_lines_pdbpp(lines, lineno)

    def _print_lines_pdbpp(self, lines, lineno, print_markers=True):
        exc_lineno = self.tb_lineno.get(self.curframe, None)
        lines = [line[:-1] for line in lines] # remove the trailing '\n'
        lines = [line.replace('\t', '    ') for line in lines] # force tabs to 4 spaces
        width, height = self.get_terminal_size()

        if self.config.truncate_long_lines:
            maxlength = max(width - 9, 16)
            lines = [line[:maxlength] for line in lines]
        else:
            maxlength = max(map(len, lines))

        if self.config.highlight:
            lines = [line.ljust(maxlength) for line in lines]
            src = self.format_source('\n'.join(lines))
            lines = src.splitlines()
        if height >= 6:
            last_marker_line = max(
                self.curframe.f_lineno,
                exc_lineno if exc_lineno else 0) - lineno
            if last_marker_line >= 0:
                maxlines = last_marker_line + height * 2 // 3
                if len(lines) > maxlines:
                    lines = lines[:maxlines]
                    lines.append('...')
        for i, line in enumerate(lines):
            marker = ''
            if lineno == self.curframe.f_lineno and print_markers:
                marker = '->'
            elif lineno == exc_lineno and print_markers:
                marker = '>>'
            lines[i] = self.format_line(lineno, marker, line)
            lineno += 1
        print('\n'.join(lines), file=self.stdout)

    do_ll = do_longlist

    def do_list(self, arg):
        oldstdout = self.stdout
        self.stdout = StringIO()
        pdb.Pdb.do_list(self, arg)
        src = self.format_source(self.stdout.getvalue())
        self.stdout = oldstdout
        print(src, file=self.stdout, end='')

    do_list.__doc__ = pdb.Pdb.do_list.__doc__
    do_l = do_list

    def do_continue(self, arg):
        if arg != '':
            self.do_tbreak(arg)
        return pdb.Pdb.do_continue(self, '')
    do_continue.__doc__ = pdb.Pdb.do_continue.__doc__
    do_c = do_cont = do_continue

    def do_pp(self, arg):
        width, height = self.get_terminal_size()
        try:
            pprint.pprint(self._getval(arg), self.stdout, width=width)
        except:
            pass
    do_pp.__doc__ = pdb.Pdb.do_pp.__doc__

    def do_debug(self, arg):
        # this is a hack (as usual :-))
        #
        # inside the original do_debug, there is a call to the global "Pdb" to
        # instantiate the recursive debugger: we want to intercept this call
        # and instantiate *our* Pdb, passing the our custom config. So, we
        # dynamically rebind the globals
        #
        def new_pdb_with_config(*args):
            kwds = dict(Config=self.ConfigFactory)
            return self.__class__(*args, **kwds)
        newglobals = {
            'Pdb': new_pdb_with_config,
            'sys': sys,
            }
        if sys.version_info < (3, ):
            do_debug_func = pdb.Pdb.do_debug.im_func
        else:
            do_debug_func = pdb.Pdb.do_debug

        orig_do_debug = rebind_globals(do_debug_func, newglobals)
        return orig_do_debug(self, arg)
    do_debug.__doc__ = pdb.Pdb.do_debug.__doc__

    def do_interact(self, arg):
        """
        interact

        Start an interative interpreter whose global namespace
        contains all the names found in the current scope.
        """
        ns = self.curframe.f_globals.copy()
        ns.update(self.curframe.f_locals)
        code.interact("*interactive*", local=ns)

    def do_track(self, arg):
        """
        track expression

        Display a graph showing which objects are referred by the
        value of the expression.  This command requires pypy to be in
        the current PYTHONPATH.
        """
        try:
            from rpython.translator.tool.reftracker import track
        except ImportError:
            print('** cannot import pypy.translator.tool.reftracker **', file=self.stdout)
            return
        try:
            val = self._getval(arg)
        except:
            pass
        else:
            track(val)

    def _get_display_list(self):
        return self.display_list.setdefault(self.curframe, {})

    def _getval_or_undefined(self, arg):
        try:
            return eval(arg, self.curframe.f_globals,
                        self.curframe.f_locals)
        except NameError:
            return undefined

    def do_display(self, arg):
        """
        display expression

        Add expression to the display list; expressions in this list
        are evaluated at each step, and printed every time its value
        changes.

        WARNING: since the expressions is evaluated multiple time, pay
        attention not to put expressions with side-effects in the
        display list.
        """
        try:
            value = self._getval_or_undefined(arg)
        except:
            return
        self._get_display_list()[arg] = value

    def do_undisplay(self, arg):
        """
        undisplay expression

        Remove expression from the display list.
        """
        try:
            del self._get_display_list()[arg]
        except KeyError:
            print('** %s not in the display list **' % arg, file=self.stdout)

    def _print_if_sticky(self):
        if self.sticky:
            self.stdout.write(CLEARSCREEN)
            frame, lineno = self.stack[self.curindex]
            filename = self.canonic(frame.f_code.co_filename)
            s = '> %s(%r)' % (filename, lineno)
            print(s, file=self.stdout)
            print(file=self.stdout)
            sticky_range = self.sticky_ranges.get(self.curframe, None)
            self._printlonglist(sticky_range)

            if '__exception__' in frame.f_locals:
                exc = frame.f_locals['__exception__']
                if len(exc) == 2:
                    exc_type, exc_value = exc
                    s = ''
                    try:
                        try:
                            s = exc_type.__name__
                        except AttributeError:
                            s = str(exc_type)
                        if exc_value is not None:
                            s += ': '
                            s += str(exc_value)
                    except KeyboardInterrupt:
                        raise
                    except:
                        s += '(unprintable exception)'
                    print(Color.set(self.config.line_number_color, ' ' + s),
                          file=self.stdout)
                    return
            if '__return__' in frame.f_locals:
                rv = frame.f_locals['__return__']
                try:
                    s = repr(rv)
                except KeyboardInterrupt:
                    raise
                except:
                    s = '(unprintable return value)'
                print(Color.set(self.config.line_number_color, ' return ' + s),
                      file=self.stdout)

    def do_sticky(self, arg):
        """
        sticky [start end]

        Toggle sticky mode. When in sticky mode, it clear the screen
        and longlist the current functions, making the source
        appearing always in the same position. Useful to follow the
        flow control of a function when doing step-by-step execution.

        If ``start`` and ``end`` are given, sticky mode is enabled and
        only lines within that range (extremes included) will be
        displayed.
        """
        if arg:
            try:
                start, end = map(int, arg.split())
            except ValueError:
                print('** Error when parsing argument: %s **' % arg,
                      file=self.stdout)
                return
            self.sticky = True
            self.sticky_ranges[self.curframe] = start, end+1
        else:
            self.sticky = not self.sticky
            self.sticky_range = None
        self._print_if_sticky()

    def print_stack_trace(self):
        try:
            for frame_index, frame_lineno in enumerate(self.stack):
                self.print_stack_entry(frame_lineno, frame_index=frame_index)
        except KeyboardInterrupt:
            pass

    def print_stack_entry(self,
            frame_lineno, prompt_prefix=pdb.line_prefix, frame_index=None):
        frame_index = (frame_index if frame_index is not None else
                       self.curindex)
        frame, lineno = frame_lineno
        if frame is self.curframe:
            print('[%d] >' % frame_index, file=self.stdout, end=' ')
        else:
            print('[%d]  ' % frame_index, file=self.stdout, end=' ')
        print(self.format_stack_entry(frame_lineno,
                                      prompt_prefix),
              file=self.stdout)

    def print_current_stack_entry(self):
        if self.sticky:
            self._print_if_sticky()
        else:
            self.print_stack_entry(self.stack[self.curindex])

    def preloop(self):
        self._print_if_sticky()
        display_list = self._get_display_list()
        for expr, oldvalue in display_list.items():
            newvalue = self._getval_or_undefined(expr)
            # check for identity first; this prevents custom __eq__ to
            # be called at every loop, and also prevents instances
            # whose fields are changed to be displayed
            if newvalue is not oldvalue or newvalue != oldvalue:
                display_list[expr] = newvalue
                print('%s: %r --> %r' % (expr, oldvalue, newvalue),
                      file=self.stdout)


    def _get_position_of_arg(self, arg):
        try:
            obj = self._getval(arg)
        except:
            return None, None, None
        if isinstance(obj, str):
            return obj, 1, None
        try:
            filename = inspect.getabsfile(obj)
            lines, lineno = inspect.getsourcelines(obj)
        except (IOError, TypeError) as e:
            print('** Error: %s **' % e, file=self.stdout)
            return None, None, None
        return filename, lineno, lines

    def do_source(self, arg):
        _, lineno, lines = self._get_position_of_arg(arg)
        if lineno is None:
            return
        self._print_lines_pdbpp(lines, lineno, print_markers=False)

    def do_frame(self, arg):
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if arg < 0 or arg >= len(self.stack):
            print('*** Out of range', file=self.stdout)
        else:
            self.curindex = arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_f = do_frame

    def do_up(self, arg='1'):
        arg = '1' if arg == '' else arg
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if self.curindex - arg < 0:
            print('*** Oldest frame', file=self.stdout)
        else:
            self.curindex = self.curindex - arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_up.__doc__ = pdb.Pdb.do_up.__doc__
    do_u = do_up

    def do_down(self, arg='1'):
        arg = '1' if arg == '' else arg
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if self.curindex + arg >= len(self.stack):
            print('*** Newest frame', file=self.stdout)
        else:
            self.curindex = self.curindex + arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_down.__doc__ = pdb.Pdb.do_down.__doc__
    do_d = do_down

    def get_terminal_size(self):
        try:
            import termios, fcntl, struct
            call = fcntl.ioctl(0, termios.TIOCGWINSZ, "\x00"*8)
            height, width = struct.unpack("hhhh", call)[:2]
        except (SystemExit, KeyboardInterrupt) as e:
            raise
        except:
            width = int(os.environ.get('COLUMNS', 80))
            height = int(os.environ.get('COLUMNS', 24))
        # Work around above returning width, height = 0, 0 in Emacs
        width = width if width != 0 else 80
        height = height if height != 0 else 24
        return width, height

    def _open_editor(self, editor, lineno, filename):
        filename = filename.replace('"', '\\"')
        os.system('%s +%d "%s"' % (editor, lineno, filename))

    def _get_current_position(self):
        frame = self.curframe
        lineno = frame.f_lineno
        filename = os.path.abspath(frame.f_code.co_filename)
        return filename, lineno

    def do_edit(self, arg):
        "Open an editor visiting the current file at the current line"
        if arg == '':
            filename, lineno = self._get_current_position()
        else:
            filename, lineno, _ = self._get_position_of_arg(arg)
            if filename is None:
                return
        # this case handles code generated with py.code.Source()
        # filename is something like '<0-codegen foo.py:18>'
        match = re.match(r'.*<\d+-codegen (.*):(\d+)>', filename)
        if match:
            filename = match.group(1)
            lineno = int(match.group(2))
        editor = self.config.editor
        self._open_editor(editor, lineno, filename)

    do_ed = do_edit

    def _get_history(self):
        return [s for s in self.history if not side_effects_free.match(s)]

    def _get_history_text(self):
        import linecache
        line = linecache.getline(self.start_filename, self.start_lineno)
        nspaces = len(line) - len(line.lstrip())
        indent = ' ' * nspaces
        history = [indent + s for s in self._get_history()]
        return '\n'.join(history) + '\n'

    def _open_stdin_paste(self, stdin_paste, lineno, filename, text):
        proc = subprocess.Popen([stdin_paste, '+%d' % lineno, filename],
                                stdin = subprocess.PIPE)
        proc.stdin.write(text)
        proc.stdin.close()

    def _put(self, text):
        stdin_paste = self.config.stdin_paste
        if stdin_paste is None:
            print('** Error: the "stdin_paste" option is not configured **', file=self.stdout)
        filename = self.start_filename
        lineno = self.start_lineno
        self._open_stdin_paste(stdin_paste, lineno, filename, text)

    def do_put(self, arg):
        text = self._get_history_text()
        self._put(text)

    def do_paste(self, arg):
        arg = arg.strip()
        old_stdout = self.stdout
        self.stdout = StringIO()
        self.onecmd(arg)
        text = self.stdout.getvalue()
        self.stdout = old_stdout
        sys.stdout.write(text)
        self._put(text)
示例#21
0
def test_complete_exception():
    compl = Completer({}, ConfigForTest)
    assert compl.attr_matches('xxx.') == []
示例#22
0
def test_complete_invalid_attr():
    compl = Completer({'str': str}, ConfigForTest)
    assert compl.attr_matches('str.xx') == []
示例#23
0
文件: pdb.py 项目: holycrepe/pdbpp
class Pdb(pdb.Pdb, ConfigurableClass):

    DefaultConfig = DefaultConfig
    config_filename = '.pdbrc.py'

    def __init__(self, *args, **kwds):
        self.ConfigFactory = kwds.pop('Config', None)
        self.start_lineno = kwds.pop('start_lineno', None)
        self.start_filename = kwds.pop('start_filename', None)
        self.config = self.get_config(self.ConfigFactory)
        self.config.setup(self)
        if self.config.disable_pytest_capturing:
            self._disable_pytest_capture_maybe()
        pdb.Pdb.__init__(self, *args, **kwds)
        self.prompt = self.config.prompt

        self.mycompleter = None
        self.display_list = {} # frame --> (name --> last seen value)
        self.sticky = self.config.sticky_by_default
        self.sticky_ranges = {} # frame --> (start, end)
        self.tb_lineno = {} # frame --> lineno where the exception raised
        self.history = []
        self.show_hidden_frames = False
        self.hidden_frames = []
        self.stdout = self.ensure_file_can_write_unicode(self.stdout)

    def ensure_file_can_write_unicode(self, f):
        # Wrap with an encoder, but only if not already wrapped
        if not hasattr(f, 'stream') and \
           getattr(f, 'encoding', False) and f.encoding.lower() != 'utf-8':
            f = codecs.getwriter('utf-8')(f)

        return f

    def _disable_pytest_capture_maybe(self):
        try:
            import py.test
            py.test.config # force to raise ImportError if pytest is not
                           # installed
        except (ImportError, AttributeError):
            return
        try:
            capman = py.test.config.pluginmanager.getplugin('capturemanager')
            capman.suspendcapture()
        except KeyError:
            pass
        except AttributeError:
            pass # newer py.test with support ready, or very old py.test for
                 # which this hack does not work

    def interaction(self, frame, traceback):
        if self.config.exec_if_unfocused:
            self.exec_if_unfocused()
        self.setup(frame, traceback)
        self.print_stack_entry(self.stack[self.curindex])
        self.print_hidden_frames_count()
        completer = fancycompleter.setup()
        old_completer = completer.config.readline.get_completer()
        completer.config.readline.set_completer(self.complete)
        self.config.before_interaction_hook(self)
        self.cmdloop()
        completer.config.readline.set_completer(old_completer)
        self.forget()

    def print_hidden_frames_count(self):
        n = len(self.hidden_frames)
        if n and self.config.show_hidden_frames_count:
            plural = n>1 and 's' or ''
            print("   %d frame%s hidden (try 'help hidden_frames')" % (n, plural),
                  file=self.stdout)

    def exec_if_unfocused(self):
        import os
        import wmctrl
        term = os.getenv('TERM', '')
        try:
            winid = int(os.getenv('WINDOWID'))
        except (TypeError, ValueError):
            return # cannot find WINDOWID of the terminal
        active_win = wmctrl.Window.get_active()
        if not active_win or (int(active_win.id, 16) != winid) and \
           not (active_win.wm_class == 'emacs.Emacs' and term.startswith('eterm')):
            os.system(self.config.exec_if_unfocused)

    def setup(self, frame, tb):
        pdb.Pdb.setup(self, frame, tb)
        while tb:
            lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
            self.tb_lineno[tb.tb_frame] = lineno
            tb = tb.tb_next

    @staticmethod
    def is_hidden_frame(frame):
        return is_hidden_frame(frame)

    def _is_hidden(self, frame):
        if not self.config.enable_hidden_frames:
            return False
        return self.is_hidden_frame(frame)

    def get_stack(self, f, t):
        # show all the frames, except the ones that explicitly ask to be hidden
        fullstack, _ = pdb.Pdb.get_stack(self, f, t)
        self.fullstack = fullstack
        return self.compute_stack(fullstack)

    def compute_stack(self, fullstack):
        self.hidden_frames = []
        newstack = []
        for frame, lineno in fullstack:
            if self._is_hidden(frame) and not self.show_hidden_frames:
                self.hidden_frames.append((frame, lineno))
            else:
                newstack.append((frame, lineno))
        stack = newstack
        i = max(0, len(stack) - 1)
        return stack, i

    def refresh_stack(self):
        """
        Recompute the stack after e.g. show_hidden_frames has been modified
        """
        self.stack, _ = self.compute_stack(self.fullstack)
        # find the current frame in the new stack
        for i, (frame, _) in enumerate(self.stack):
            if frame is self.curframe:
                self.curindex = i
                break
        else:
            self.curindex = len(self.stack)-1
            self.curframe = self.stack[-1][0]
            self.print_current_stack_entry()

    def forget(self):
        pdb.Pdb.forget(self)
        self.raise_lineno = {}

    def complete(self, text, state):
        if state == 0:
            mydict = self.curframe.f_globals.copy()
            mydict.update(self.curframe.f_locals)
            self.mycompleter = Completer(mydict)
        return self.mycompleter.complete(text, state)

    def _init_pygments(self):
        if not self.config.use_pygments:
            return False
        try:
            from pygments.lexers import PythonLexer
            from pygments.formatters import TerminalFormatter, Terminal256Formatter
        except ImportError:
            return False

        if hasattr(self, '_fmt'):
            return True

        Formatter = (Terminal256Formatter
                     if self.config.use_terminal256formatter
                        and '256color' in os.environ.get('TERM', '')
                     else TerminalFormatter)

        self._fmt = Formatter(bg=self.config.bg,
                              colorscheme=self.config.colorscheme)
        self._lexer = PythonLexer()
        return True


    stack_entry_regexp = re.compile(r'(.*?)\(([0-9]+?)\)(.*)', re.DOTALL)
    #
    def format_stack_entry(self, frame_lineno, lprefix=': '):
        entry = pdb.Pdb.format_stack_entry(self, frame_lineno, lprefix)
        entry = self.try_to_decode(entry)
        if self.config.highlight:
            match = self.stack_entry_regexp.match(entry)
            if match:
                filename, lineno, other = match.groups()
                filename = Color.set(self.config.filename_color, filename)
                lineno = Color.set(self.config.line_number_color, lineno)
                entry = '%s(%s)%s' % (filename, lineno, other)
        return entry

    def try_to_decode(self, s):
        for encoding in self.config.encodings:
            try:
                return s.decode(encoding)
            except (UnicodeDecodeError, AttributeError):
                pass
        return s

    def format_source(self, src):
        if not self._init_pygments():
            return src
        from pygments import highlight
        src = self.try_to_decode(src)
        return highlight(src, self._lexer, self._fmt)

    def format_line(self, lineno, marker, line):
        lineno = '%4d' % lineno
        if self.config.highlight:
            lineno = Color.set(self.config.line_number_color, lineno)
        line = '%s  %2s %s' % (lineno, marker, line)
        if self.config.highlight and marker == '->':
            line = setbgcolor(line, self.config.current_line_color)
        return line

    def parseline(self, line):
        if line.startswith('!!'):
            # force the "standard" behaviour, i.e. first check for the
            # command, then for the variable name to display
            line = line[2:]
            return pdb.Pdb.parseline(self, line)
        # pdb++ "smart command mode": don't execute commands if a variable
        # with the name exits in the current contex; this prevents pdb to quit
        # if you type e.g. 'r[0]' by mystake.
        cmd, arg, newline = pdb.Pdb.parseline(self, line)

        if arg and arg.endswith('?'):
            if hasattr(self, 'do_' + cmd):
                cmd, arg = ('help', cmd)
            elif arg.endswith('??'):
                arg = cmd + arg.split('?')[0]
                cmd = 'source'
                self.do_inspect(arg)
                self.stdout.write('%-28s\n' % Color.set(Color.red, 'Source:'))
            else:
                arg = cmd + arg.split('?')[0]
                cmd = 'inspect'
                return cmd, arg, newline

        if cmd and hasattr(self, 'do_'+cmd) and (cmd in self.curframe.f_globals or
                                                 cmd in self.curframe.f_locals or
                                                 arg.startswith('=')):
            line = '!' + line
            return pdb.Pdb.parseline(self, line)
        return cmd, arg, newline

    def do_inspect(self, arg):
        obj = self._getval(arg)

        data = OrderedDict()
        data['Type'] = type(obj).__name__
        data['String Form'] = str(obj).strip()
        if hasattr(obj, '__len__'):
            data['Length'] = len(obj)
        try:
            data['File'] = inspect.getabsfile(obj)
        except TypeError:
            pass

        if (isinstance(obj, type)
                and hasattr(obj, '__init__')
                and getattr(obj, '__module__') != '__builtin__'):
            # Class - show definition and docstring for constructor
            data['Docstring'] = obj.__doc__
            data['Constructor information'] = ''
            try:
                data[' Definition'] = '%s%s' % (arg, signature(obj))
            except ValueError:
                pass
            data[' Docstring'] = obj.__init__.__doc__
        else:
            try:
                data['Definition'] = '%s%s' % (arg, signature(obj))
            except (TypeError, ValueError):
                pass
            data['Docstring'] = obj.__doc__

        for key, value in data.items():
            formatted_key = Color.set(Color.red, key + ':')
            self.stdout.write('%-28s %s\n' % (formatted_key, value))

    def default(self, line):
        self.history.append(line)
        return pdb.Pdb.default(self, line)

    def do_help(self, arg):
        try:
            return pdb.Pdb.do_help(self, arg)
        except AttributeError:
            print("*** No help for '{command}'".format(command=arg),
                  file=self.stdout)
    do_help.__doc__ = pdb.Pdb.do_help.__doc__

    def help_hidden_frames(self):
        print("""\
Some frames might be marked as "hidden": by default, hidden frames are not
shown in the stack trace, and cannot be reached using ``up`` and ``down``.
You can use ``hf_unhide`` to tell pdb to ignore the hidden status (i.e., to
treat hidden frames as normal ones), and ``hf_hide`` to hide them again.
``hf_list`` prints a list of hidden frames.

Frames can marked as hidden in the following ways:

- by using the @pdb.hideframe function decorator

- by having __tracebackhide__=True in the locals or the globals of the function
  (this hides py.test internal stuff)

- by having __unittest=True in the globals of the function (this hides
  unittest internal stuff)
""", file=self.stdout)

    def do_hf_unhide(self, arg):
        """
        {hf_show}
        unhide hidden frames, i.e. make it possible to ``up`` or ``down``
        there
        """
        self.show_hidden_frames = True
        self.refresh_stack()

    def do_hf_hide(self, arg):
        """
        {hf_hide}
        (re)hide hidden frames, if they have been unhidden by ``hf_unhide``
        """
        self.show_hidden_frames = False
        self.refresh_stack()

    def do_hf_list(self, arg):
        for frame_lineno in self.hidden_frames:
            print(self.format_stack_entry(frame_lineno, pdb.line_prefix),
                  file=self.stdout)

    def do_longlist(self, arg):
        """
        {longlist|ll}
        List source code for the current function.

        Differently than list, the whole function is displayed; the
        current line is marked with '->'.  In case of post-mortem
        debugging, the line which effectively raised the exception is
        marked with '>>'.

        If the 'highlight' config option is set and pygments is
        installed, the source code is colorized.
        """
        self.lastcmd = 'longlist'
        self._printlonglist()

    def _printlonglist(self, linerange=None):
        try:
            if self.curframe.f_code.co_name == '<module>':
                # inspect.getsourcelines is buggy in this case: if we just
                # pass the frame, it returns the source for the first function
                # defined in the module.  Instead, we want the full source
                # code of the module
                lines, _ = inspect.findsource(self.curframe)
                lineno = 1
            else:
                try:
                    lines, lineno = inspect.getsourcelines(self.curframe)
                except Exception as e:
                    print('** Error in inspect.getsourcelines: %s **' % e, file=self.stdout)
                    return
        except IOError as e:
            print('** Error: %s **' % e, file=self.stdout)
            return
        if linerange:
            start, end = linerange
            start = max(start, lineno)
            end = min(end, lineno+len(lines))
            lines = lines[start-lineno:end-lineno]
            lineno = start
        self._print_lines_pdbpp(lines, lineno)

    def _print_lines_pdbpp(self, lines, lineno, print_markers=True):
        exc_lineno = self.tb_lineno.get(self.curframe, None)
        lines = [line[:-1] for line in lines] # remove the trailing '\n'
        lines = [line.replace('\t', '    ') for line in lines] # force tabs to 4 spaces
        width, height = self.get_terminal_size()

        if self.config.truncate_long_lines:
            maxlength = max(width - 9, 16)
            lines = [line[:maxlength] for line in lines]
        else:
            maxlength = max(map(len, lines))

        if self.config.highlight:
            lines = [line.ljust(maxlength) for line in lines]
            src = self.format_source('\n'.join(lines))
            lines = src.splitlines()
        if height >= 6:
            last_marker_line = max(
                self.curframe.f_lineno,
                exc_lineno if exc_lineno else 0) - lineno
            if last_marker_line >= 0:
                maxlines = last_marker_line + height * 2 // 3
                if len(lines) > maxlines:
                    lines = lines[:maxlines]
                    lines.append('...')
        for i, line in enumerate(lines):
            marker = ''
            if lineno == self.curframe.f_lineno and print_markers:
                marker = '->'
            elif lineno == exc_lineno and print_markers:
                marker = '>>'
            lines[i] = self.format_line(lineno, marker, line)
            lineno += 1
        print('\n'.join(lines), file=self.stdout)

    do_ll = do_longlist

    def do_list(self, arg):
        oldstdout = self.stdout
        self.stdout = StringIO()
        pdb.Pdb.do_list(self, arg)
        src = self.format_source(self.stdout.getvalue())
        self.stdout = oldstdout
        print(src, file=self.stdout, end='')

    do_list.__doc__ = pdb.Pdb.do_list.__doc__
    do_l = do_list

    def do_continue(self, arg):
        if arg != '':
            self.do_tbreak(arg)
        return pdb.Pdb.do_continue(self, '')
    do_continue.__doc__ = pdb.Pdb.do_continue.__doc__
    do_c = do_cont = do_continue

    def do_pp(self, arg):
        width, height = self.get_terminal_size()
        try:
            pprint.pprint(self._getval(arg), self.stdout, width=width)
        except:
            pass
    do_pp.__doc__ = pdb.Pdb.do_pp.__doc__

    def do_debug(self, arg):
        # this is a hack (as usual :-))
        #
        # inside the original do_debug, there is a call to the global "Pdb" to
        # instantiate the recursive debugger: we want to intercept this call
        # and instantiate *our* Pdb, passing the our custom config. So, we
        # dynamically rebind the globals
        #
        def new_pdb_with_config(*args):
            kwds = dict(Config=self.ConfigFactory)
            return self.__class__(*args, **kwds)
        newglobals = {
            'Pdb': new_pdb_with_config,
            'sys': sys,
            }
        if sys.version_info < (3, ):
            do_debug_func = pdb.Pdb.do_debug.im_func
        else:
            do_debug_func = pdb.Pdb.do_debug

        orig_do_debug = rebind_globals(do_debug_func, newglobals)
        return orig_do_debug(self, arg)
    do_debug.__doc__ = pdb.Pdb.do_debug.__doc__

    def do_interact(self, arg):
        """
        interact

        Start an interative interpreter whose global namespace
        contains all the names found in the current scope.
        """
        ns = self.curframe.f_globals.copy()
        ns.update(self.curframe.f_locals)
        code.interact("*interactive*", local=ns)

    def do_track(self, arg):
        """
        track expression

        Display a graph showing which objects are referred by the
        value of the expression.  This command requires pypy to be in
        the current PYTHONPATH.
        """
        try:
            from rpython.translator.tool.reftracker import track
        except ImportError:
            print('** cannot import pypy.translator.tool.reftracker **', file=self.stdout)
            return
        try:
            val = self._getval(arg)
        except:
            pass
        else:
            track(val)

    def _get_display_list(self):
        return self.display_list.setdefault(self.curframe, {})

    def _getval_or_undefined(self, arg):
        try:
            return eval(arg, self.curframe.f_globals,
                        self.curframe.f_locals)
        except NameError:
            return undefined

    def do_display(self, arg):
        """
        display expression

        Add expression to the display list; expressions in this list
        are evaluated at each step, and printed every time its value
        changes.

        WARNING: since the expressions is evaluated multiple time, pay
        attention not to put expressions with side-effects in the
        display list.
        """
        try:
            value = self._getval_or_undefined(arg)
        except:
            return
        self._get_display_list()[arg] = value

    def do_undisplay(self, arg):
        """
        undisplay expression

        Remove expression from the display list.
        """
        try:
            del self._get_display_list()[arg]
        except KeyError:
            print('** %s not in the display list **' % arg, file=self.stdout)

    def _print_if_sticky(self):
        if self.sticky:
            self.stdout.write(CLEARSCREEN)
            frame, lineno = self.stack[self.curindex]
            filename = self.canonic(frame.f_code.co_filename)
            s = '> %s(%r)' % (filename, lineno)
            print(s, file=self.stdout)
            print(file=self.stdout)
            sticky_range = self.sticky_ranges.get(self.curframe, None)
            self._printlonglist(sticky_range)

            if '__exception__' in frame.f_locals:
                exc = frame.f_locals['__exception__']
                if len(exc) == 2:
                    exc_type, exc_value = exc
                    s = ''
                    try:
                        try:
                            s = exc_type.__name__
                        except AttributeError:
                            s = str(exc_type)
                        if exc_value is not None:
                            s += ': '
                            s += str(exc_value)
                    except KeyboardInterrupt:
                        raise
                    except:
                        s += '(unprintable exception)'
                    print(Color.set(self.config.line_number_color, ' ' + s),
                          file=self.stdout)
                    return
            if '__return__' in frame.f_locals:
                rv = frame.f_locals['__return__']
                try:
                    s = repr(rv)
                except KeyboardInterrupt:
                    raise
                except:
                    s = '(unprintable return value)'
                print(Color.set(self.config.line_number_color, ' return ' + s),
                      file=self.stdout)

    def do_sticky(self, arg):
        """
        sticky [start end]

        Toggle sticky mode. When in sticky mode, it clear the screen
        and longlist the current functions, making the source
        appearing always in the same position. Useful to follow the
        flow control of a function when doing step-by-step execution.

        If ``start`` and ``end`` are given, sticky mode is enabled and
        only lines within that range (extremes included) will be
        displayed.
        """
        if arg:
            try:
                start, end = map(int, arg.split())
            except ValueError:
                print('** Error when parsing argument: %s **' % arg,
                      file=self.stdout)
                return
            self.sticky = True
            self.sticky_ranges[self.curframe] = start, end+1
        else:
            self.sticky = not self.sticky
            self.sticky_range = None
        self._print_if_sticky()

    def print_stack_trace(self):
        try:
            for frame_index, frame_lineno in enumerate(self.stack):
                self.print_stack_entry(frame_lineno, frame_index=frame_index)
        except KeyboardInterrupt:
            pass

    def print_stack_entry(self,
            frame_lineno, prompt_prefix=pdb.line_prefix, frame_index=None):
        frame_index = (frame_index if frame_index is not None else
                       self.curindex)
        frame, lineno = frame_lineno
        if frame is self.curframe:
            print('[%d] >' % frame_index, file=self.stdout, end=' ')
        else:
            print('[%d]  ' % frame_index, file=self.stdout, end=' ')
        print(self.format_stack_entry(frame_lineno,
                                      prompt_prefix),
              file=self.stdout)

    def print_current_stack_entry(self):
        if self.sticky:
            self._print_if_sticky()
        else:
            self.print_stack_entry(self.stack[self.curindex])

    def preloop(self):
        self._print_if_sticky()
        display_list = self._get_display_list()
        for expr, oldvalue in display_list.items():
            newvalue = self._getval_or_undefined(expr)
            # check for identity first; this prevents custom __eq__ to
            # be called at every loop, and also prevents instances
            # whose fields are changed to be displayed
            if newvalue is not oldvalue or newvalue != oldvalue:
                display_list[expr] = newvalue
                print('%s: %r --> %r' % (expr, oldvalue, newvalue),
                      file=self.stdout)


    def _get_position_of_arg(self, arg):
        try:
            obj = self._getval(arg)
        except:
            return None, None, None
        if isinstance(obj, str):
            return obj, 1, None
        try:
            filename = inspect.getabsfile(obj)
            lines, lineno = inspect.getsourcelines(obj)
        except (IOError, TypeError) as e:
            print('** Error: %s **' % e, file=self.stdout)
            return None, None, None
        return filename, lineno, lines

    def do_source(self, arg):
        _, lineno, lines = self._get_position_of_arg(arg)
        if lineno is None:
            return
        self._print_lines_pdbpp(lines, lineno, print_markers=False)

    def do_frame(self, arg):
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if arg < 0 or arg >= len(self.stack):
            print('*** Out of range', file=self.stdout)
        else:
            self.curindex = arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_f = do_frame

    def do_up(self, arg='1'):
        arg = '1' if arg == '' else arg
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if self.curindex - arg < 0:
            print('*** Oldest frame', file=self.stdout)
        else:
            self.curindex = self.curindex - arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_up.__doc__ = pdb.Pdb.do_up.__doc__
    do_u = do_up

    def do_down(self, arg='1'):
        arg = '1' if arg == '' else arg
        try:
            arg = int(arg)
        except (ValueError, TypeError):
            print('*** Expected a number, got "{0}"'.format(arg), file=self.stdout)
            return
        if self.curindex + arg >= len(self.stack):
            print('*** Newest frame', file=self.stdout)
        else:
            self.curindex = self.curindex + arg
            self.curframe = self.stack[self.curindex][0]
            self.curframe_locals = self.curframe.f_locals
            self.print_current_stack_entry()
            self.lineno = None
    do_down.__doc__ = pdb.Pdb.do_down.__doc__
    do_d = do_down

    def get_terminal_size(self):
        try:
            import termios, fcntl, struct
            call = fcntl.ioctl(0, termios.TIOCGWINSZ, "\x00"*8)
            height, width = struct.unpack("hhhh", call)[:2]
        except (SystemExit, KeyboardInterrupt) as e:
            raise
        except:
            width = int(os.environ.get('COLUMNS', 80))
            height = int(os.environ.get('COLUMNS', 24))
        # Work around above returning width, height = 0, 0 in Emacs
        width = width if width != 0 else 80
        height = height if height != 0 else 24
        return width, height

    def _open_editor(self, editor, lineno, filename):
        filename = filename.replace("'", "\\'")
        os.system("%s +%d '%s'" % (editor, lineno, filename))

    def _get_current_position(self):
        frame = self.curframe
        lineno = frame.f_lineno
        filename = os.path.abspath(frame.f_code.co_filename)
        return filename, lineno

    def do_edit(self, arg):
        "Open an editor visiting the current file at the current line"
        if arg == '':
            filename, lineno = self._get_current_position()
        else:
            filename, lineno, _ = self._get_position_of_arg(arg)
            if filename is None:
                return
        # this case handles code generated with py.code.Source()
        # filename is something like '<0-codegen foo.py:18>'
        match = re.match(r'.*<\d+-codegen (.*):(\d+)>', filename)
        if match:
            filename = match.group(1)
            lineno = int(match.group(2))
        editor = self.config.editor
        self._open_editor(editor, lineno, filename)

    do_ed = do_edit

    def _get_history(self):
        return [s for s in self.history if not side_effects_free.match(s)]

    def _get_history_text(self):
        import linecache
        line = linecache.getline(self.start_filename, self.start_lineno)
        nspaces = len(line) - len(line.lstrip())
        indent = ' ' * nspaces
        history = [indent + s for s in self._get_history()]
        return '\n'.join(history) + '\n'

    def _open_stdin_paste(self, stdin_paste, lineno, filename, text):
        proc = subprocess.Popen([stdin_paste, '+%d' % lineno, filename],
                                stdin = subprocess.PIPE)
        proc.stdin.write(text)
        proc.stdin.close()

    def _put(self, text):
        stdin_paste = self.config.stdin_paste
        if stdin_paste is None:
            print('** Error: the "stdin_paste" option is not configured **', file=self.stdout)
        filename = self.start_filename
        lineno = self.start_lineno
        self._open_stdin_paste(stdin_paste, lineno, filename, text)

    def do_put(self, arg):
        text = self._get_history_text()
        self._put(text)

    def do_paste(self, arg):
        arg = arg.strip()
        old_stdout = self.stdout
        self.stdout = StringIO()
        self.onecmd(arg)
        text = self.stdout.getvalue()
        self.stdout = old_stdout
        sys.stdout.write(text)
        self._put(text)