hgext/pager.py
author Brodie Rao <brodie@sf.io>
Fri, 11 May 2012 15:45:37 +0200
branchstable
changeset 16631 369741ef7253
parent 14945 11aad09a6370
child 16632 0c0c1101e46d
permissions -rw-r--r--
pager: preserve Hg's exit code (and fix Windows support) (issue3225) This changes how the pager extension invokes the pager. Prior to this change, the extension would fork Hg and exec the pager in the parent process. This loses Hg exit code, and it doesn't work on Windows. Now the pager is invoked using the subprocess library, and an atexit handler is registered that makes Hg wait for the pager to exit before it exits itself. Note that if you exit the pager before Hg is done running, you'll get an exit code of 255, which is caused by Python blowing up due to a broken pipe. If you set pager.quiet=True, you'll get the OS-level return code of 141.

# pager.py - display output using a pager
#
# Copyright 2008 David Soria Parra <dsp@php.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# To load the extension, add it to your configuration file:
#
#   [extension]
#   pager =
#
# Run "hg help pager" to get info on configuration.

'''browse command output with an external pager

To set the pager that should be used, set the application variable::

  [pager]
  pager = less -FRSX

If no pager is set, the pager extensions uses the environment variable
$PAGER. If neither pager.pager, nor $PAGER is set, no pager is used.

If you notice "BROKEN PIPE" error messages, you can disable them by
setting::

  [pager]
  quiet = True

You can disable the pager for certain commands by adding them to the
pager.ignore list::

  [pager]
  ignore = version, help, update

You can also enable the pager only for certain commands using
pager.attend. Below is the default list of commands to be paged::

  [pager]
  attend = annotate, cat, diff, export, glog, log, qdiff

Setting pager.attend to an empty value will cause all commands to be
paged.

If pager.attend is present, pager.ignore will be ignored.

To ignore global commands like :hg:`version` or :hg:`help`, you have
to specify them in your user configuration file.

The --pager=... option can also be used to control when the pager is
used. Use a boolean value like yes, no, on, off, or use auto for
normal behavior.
'''

import atexit, sys, os, signal, subprocess
from mercurial import commands, dispatch, util, extensions
from mercurial.i18n import _

def _runpager(p):
    pager = subprocess.Popen(p, shell=True, bufsize=-1,
                             close_fds=util.closefds, stdin=subprocess.PIPE,
                             stdout=sys.stdout, stderr=sys.stderr)

    stdout = os.dup(sys.stdout.fileno())
    stderr = os.dup(sys.stderr.fileno())
    os.dup2(pager.stdin.fileno(), sys.stdout.fileno())
    if util.isatty(sys.stderr):
        os.dup2(pager.stdin.fileno(), sys.stderr.fileno())

    @atexit.register
    def killpager():
        pager.stdin.close()
        os.dup2(stdout, sys.stdout.fileno())
        os.dup2(stderr, sys.stderr.fileno())
        pager.wait()

def uisetup(ui):
    if ui.plain() or '--debugger' in sys.argv or not util.isatty(sys.stdout):
        return

    def pagecmd(orig, ui, options, cmd, cmdfunc):
        p = ui.config("pager", "pager", os.environ.get("PAGER"))

        if p:
            attend = ui.configlist('pager', 'attend', attended)
            auto = options['pager'] == 'auto'
            always = util.parsebool(options['pager'])
            if (always or auto and
                (cmd in attend or
                 (cmd not in ui.configlist('pager', 'ignore') and not attend))):
                ui.setconfig('ui', 'formatted', ui.formatted())
                ui.setconfig('ui', 'interactive', False)
                _runpager(p)
                if ui.configbool('pager', 'quiet'):
                    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
        return orig(ui, options, cmd, cmdfunc)

    extensions.wrapfunction(dispatch, '_runcommand', pagecmd)

def extsetup(ui):
    commands.globalopts.append(
        ('', 'pager', 'auto',
         _("when to paginate (boolean, always, auto, or never)"),
         _('TYPE')))

attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']