[issue5845] rlcompleter should be enabled automatically

Steven D'Aprano report at bugs.python.org
Wed Jul 31 10:04:14 CEST 2013


Steven D'Aprano added the comment:

On 31/07/13 17:14, Larry Hastings wrote:

> IMO the optimal solution is that tab preceded by only whitespace indents, and tab preceded by any non-whitespace character attempts to complete.  Can we goad readline into behaving this way?

Yes we can. Attached are a pair of lightweight modules I've used for this for the last 3-4 years, and it works fine in Python 2.4 through 3.3. I put something like this in my startup.py:

import completer
import history
history = history.History()
completer = completer.Completer(
         bindings=(r'"\C-xo": overwrite-mode',
                   r'"\C-xd": dump-functions',
                  ))

Originally they were in a single class, but I was persuaded that it was cleaner to separate them.

----------
Added file: http://bugs.python.org/file31095/completer.py
Added file: http://bugs.python.org/file31096/history.py

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue5845>
_______________________________________
-------------- next part --------------
"""Command line completion.

This module relies on both the ``readline`` and ``rlcompleter`` modules.
Under Windows, you may be able to use the third-party ``pyreadline`` module
(untested).

Creating a ``Completer`` instance enables readline completion:

>>> completer = Completer()  #doctest: +SKIP


By default, the TAB key is used for both indenting and completion. See
the ``Completer`` class for further instructions, including how to change
this behaviour.
"""

# Keep this module compatible with Python 2.4 and better.


# TODO: add "operate and go next" functionality like in bash.
# TODO: add filename completion.


try:
    # Everything depends on readline.
    import readline
except ImportError:
    # May be Windows, so try using a substitute.
    import pyreadline as readline

import rlcompleter


def using_libedit():
    """Return True if the underlying readline library is libedit."""
    # This tests for the libedit library instead of libreadline, which
    # may indicate OS-X or *BSD - See http://bugs.python.org/issue10666
    #
    # FIXME This is the canonical test as suggested by the docs, but
    # surely there is a better test than this? Perhaps something like
    # sys.platform == DARWIN?
    return 'libedit' in readline.__doc__


# Set up command line completion:
class Completer(rlcompleter.Completer):
    """Readline tab completer with optional support for indenting.

    All arguments to the class constructor are optional.

    namespace::
        None, or a namespace dict, to use for completions. See the
        ``rlcompleter`` module for more details.

    key::
        Key to use for completion. ``key`` should be a key combination
        written in the appropriate syntax for your readline library.
        If ``key`` is not given or is None, the default TAB key will be
        used:

            * if you are using libreadline, 'tab' will be used;
            * if you are using libedit, '^I' will be used.

        Any other choice for ``key`` will be used exactly as given, and
        it is your responsibility to ensure it is in the correct format
        for the underlying readline library.

    indent::
        String to insert for indents when the completer key is pressed
        at the start of the line. The default is to insert '\\t' (a
        literal tab). Another common choice is '    ' (four spaces). If
        you pass None or the empty string, pressing the completer key
        will *not* indent.

    query_items::
        The maximum number of items that the completer will show without
        asking first. The default is 30.

    bindings::
        A tuple of additional readline bindings to be parsed. As a
        convenience, if you have only one binding to use, you can pass
        it as a string rather than inside a tuple. See your operating
        system's readline documentation for syntax.

    """
    def __init__(self, namespace=None,
                # Tab completion:
                key=None, indent='\t', query_items=30,
                # Extra bindings to be used:
                bindings=(),
                ):
        # This is a classic class in Python 2.x, so no super().
        rlcompleter.Completer.__init__(self, namespace)
        self.key = key
        self.indent = indent
        self.query_items = query_items
        if isinstance(bindings, str):
            bindings = (bindings,)
        self.bindings = bindings
        self._enable()

    def completer(self, text, state):
        """Completer function with optional support for indenting.

        If self.indent is not empty or None, it will be used to indent at the
        start of lines.
        """
        # At the start of a line, indent.
        if self.indent and (text == '' or text.isspace()):
            return [self.indent, None][state]
        return rlcompleter.Completer.complete(self, text, state)

    def set_completer(self):
        """Set the completer."""
        # Remove the previous completer (possibly installed by rlcompleter).
        readline.set_completer(None)
        if using_libedit():
            cmd = 'bind %s rl_complete' % (self.key or '^I')
        else:
            cmd = '%s: complete' % (self.key or 'tab')
        readline.parse_and_bind(cmd)
        readline.set_completer(self.completer)

    def _enable(self):
        """Enable tab completion."""
        self.set_completer()
        readline.parse_and_bind(
            "set completion-query-items %d" % self.query_items)
        s = ('\x4e\x4f\x42\x4f\x44\x59\x20\x65\x78\x70\x65\x63\x74'
             '\x73\x20\x74\x68\x65\x20\x53\x70\x61\x6e\x69\x73\x68'
             '\x20\x49\x6e\x71\x75\x69\x73\x69\x74\x69\x6f\x6e\x21')
        readline.parse_and_bind(r'"\C-xi": "%s"' % s)
        for binding in self.bindings:
            readline.parse_and_bind(binding)

-------------- next part --------------
"""Enable and control command-line history.

This module relies on the ``readline`` module. When ``readline`` is not
available, e.g. under Windows, it may work using the third-party
``pyreadline`` module (untested).

Any text file can be used as a history file, each line in the file is
considered one history command.

Creating a ``History`` instance enables command-line history, reads in any
contents of the history file, and prepares to write history back to that
file when Python exits. Calling ``History()`` with no arguments uses the
default history file:

>>> history = History()  #doctest: +SKIP


See the ``History`` class for details on the arguments accepted.

You can display the last few commands by calling the instance:

>>> history(4)  #doctest: +SKIP
119: x = spam(23) + eggs(42)
120: do_this()
121: do_that()
122: do_something_else()


You can read lines from a history file at any time. The ``read_history``
method keeps any existing lines in the current history buffer; the
``load_history`` method replaces the current buffer.

You can write the current history buffer to a file at any time by calling
the ``write_history`` method. The number of lines written is controlled
by the readline ``set_history_length`` function.
"""

# Keep this module compatible with Python 2.4 and better.


try:
    # Everything depends on readline.
    import readline
except ImportError:
    # May be Windows, so try using a substitute.
    import pyreadline as readline

import atexit
import os


class History(object):
    """Enable and control command-line history.

    Arguments:

    history_file::
        Name of the history file to use. If not given, the attribute
        DEFAULT_HISTORY_FILE (defaults to '.python_history' in the
        user's home directory) is used.

    history_length::
        The maximum number of lines which will be saved to the history
        file. If not given, the attribute DEFAULT_HISTORY_LENGTH
        (defaults to 500) is used.

    """

    DEFAULT_HISTORY_FILE = '~/.python_history'
    DEFAULT_HISTORY_LENGTH = 500
    MAX_LINES_TO_SHOW = 10  # Use < 0 for unlimited.

    def __init__(self, history_file=None, history_length=None):
        if history_file is None:
            history_file = self.DEFAULT_HISTORY_FILE
        history_file = os.path.expanduser(history_file)
        self.history_file = history_file
        if history_length is None:
            history_length = self.DEFAULT_HISTORY_LENGTH
        self.history_length = history_length
        self._enable()
        
    def _enable(self):
        filename = self.history_file
        self.read_history(filename)
        readline.set_history_length(self.history_length)
        # Save the history file when exiting.
        atexit.register(readline.write_history_file, filename)

    def read_history(self, filename=None):
        """Read history from the named file (if possible).

        If filename is None or not given, use the ``history_file``
        instance attribute.

        History lines read are appended to the current history buffer.
        To replace the current buffer, use the ``load_history`` method.
        """
        if filename is None:
            filename = self.history_file
        try:
            readline.read_history_file(os.path.expanduser(filename))
        except (IOError, OSError):
            pass

    def load_history(self, filename=None):
        """Clear the current history buffer, then load history from
        the named file (if possible).

        If filename is None or not given, use the ``history_file``
        instance attribute.

        To read history lines without overwriting the current buffer,
        use the ``read_history`` method.
        """
        readline.clear_history()
        self.read_history(filename)

    def write_history(self, filename=None):
        """Write command line history to the named file without waiting
        for program exit.

        If ``filename`` is None or not given, use the ``history_file``
        instance attribute.
        """
        if filename is None:
            filename = self.history_file
        readline.write_history_file(os.path.expanduser(filename))

    def get_history_lines(self, start=1, end=None):
        """Yield history lines between ``start`` and ``end`` inclusive.

        Unlike Python indexes, the readline history lines are numbered
        from 1. If not given, ``start`` defaults to 1, and ``end``
        defaults to the current history length.
        """
        get = readline.get_history_item
        if end is None:
            end = readline.get_current_history_length()
        return (get(i) for i in range(start, end+1))
        
    def __call__(self, count=None, show_line_numbers=True):
        """Print the latest ``count`` lines from the history.

        If ``count`` is None or not given, a default number of lines
        is shown, given by the attribute MAX_LINES_TO_SHOW. Use a
        negative count to show unlimited lines.

        If ``show_line_numbers`` is true (the default), each history
        line is preceeded by the line number.
        """
        if count is None:
            count = self.MAX_LINES_TO_SHOW
        end = readline.get_current_history_length()
        if count < 0:
            start = 1
        else:
            start = max(end - count + 1, 1)
        nums = range(start, end+1)
        lines = self.get_history_lines(start, end)
        if show_line_numbers:
            # Can't use {} formatting as we have to support 2.4 and 2.5.
            template = "%(lineno)3d:  %(line)s"
        else:
            template = "%(line)s"
        for i, line in zip(nums, lines):
            print(template % {'lineno':i, 'line':line})



More information about the Python-bugs-list mailing list