# HG changeset patch # User Augie Fackler # Date 1487198871 18000 # Node ID 61b4122019d384813761c030de3e1e091d156469 # Parent 3844b3299a53b3746e1a708c84bbd6b83adddb7d pager: move pager-initiating code into core No functionality change. A previous version of this API had a category argument on ui.pager(). As I migrated the commands in core, I couldn't come up with good enough consistency in any categorization scheme so I just scrapped the whole idea. It may be worth revisiting in the future. diff -r 3844b3299a53 -r 61b4122019d3 hgext/pager.py --- a/hgext/pager.py Thu Feb 16 10:33:59 2017 -0800 +++ b/hgext/pager.py Wed Feb 15 17:47:51 2017 -0500 @@ -60,19 +60,11 @@ ''' from __future__ import absolute_import -import atexit -import os -import signal -import subprocess -import sys - from mercurial.i18n import _ from mercurial import ( cmdutil, commands, dispatch, - encoding, - error, extensions, util, ) @@ -83,48 +75,14 @@ # leave the attribute unspecified. testedwith = 'ships-with-hg-core' -def _runpager(ui, p): - pager = subprocess.Popen(p, shell=True, bufsize=-1, - close_fds=util.closefds, stdin=subprocess.PIPE, - stdout=util.stdout, stderr=util.stderr) - - # back up original file descriptors - stdoutfd = os.dup(util.stdout.fileno()) - stderrfd = os.dup(util.stderr.fileno()) - - os.dup2(pager.stdin.fileno(), util.stdout.fileno()) - if ui._isatty(util.stderr): - os.dup2(pager.stdin.fileno(), util.stderr.fileno()) - - @atexit.register - def killpager(): - if util.safehasattr(signal, "SIGINT"): - signal.signal(signal.SIGINT, signal.SIG_IGN) - # restore original fds, closing pager.stdin copies in the process - os.dup2(stdoutfd, util.stdout.fileno()) - os.dup2(stderrfd, util.stderr.fileno()) - pager.stdin.close() - pager.wait() - -def catchterm(*args): - raise error.SignalInterrupt - def uisetup(ui): - class pagerui(ui.__class__): - def _runpager(self, pagercmd): - _runpager(self, pagercmd) - - ui.__class__ = pagerui def pagecmd(orig, ui, options, cmd, cmdfunc): - p = ui.config("pager", "pager", encoding.environ.get("PAGER")) usepager = False always = util.parsebool(options['pager']) auto = options['pager'] == 'auto' - if not p or '--debugger' in sys.argv or not ui.formatted(): - pass - elif always: + if always: usepager = True elif not auto: usepager = False @@ -143,14 +101,8 @@ usepager = True break - setattr(ui, 'pageractive', usepager) - if usepager: - ui.setconfig('ui', 'formatted', ui.formatted(), 'pager') - ui.setconfig('ui', 'interactive', False, 'pager') - if util.safehasattr(signal, "SIGPIPE"): - signal.signal(signal.SIGPIPE, catchterm) - ui._runpager(p) + ui.pager('extension-via-attend-' + cmd) return orig(ui, options, cmd, cmdfunc) # Wrap dispatch._runcommand after color is loaded so color can see diff -r 3844b3299a53 -r 61b4122019d3 mercurial/ui.py --- a/mercurial/ui.py Thu Feb 16 10:33:59 2017 -0800 +++ b/mercurial/ui.py Wed Feb 15 17:47:51 2017 -0500 @@ -7,6 +7,7 @@ from __future__ import absolute_import +import atexit import collections import contextlib import errno @@ -14,7 +15,9 @@ import inspect import os import re +import signal import socket +import subprocess import sys import tempfile import traceback @@ -115,6 +118,8 @@ def find_user_password(self, *args, **kwargs): return self._get_mgr().find_user_password(*args, **kwargs) +def _catchterm(*args): + raise error.SignalInterrupt class ui(object): def __init__(self, src=None): @@ -149,6 +154,7 @@ self.fout = src.fout self.ferr = src.ferr self.fin = src.fin + self.pageractive = src.pageractive self._tcfg = src._tcfg.copy() self._ucfg = src._ucfg.copy() @@ -166,6 +172,7 @@ self.fout = util.stdout self.ferr = util.stderr self.fin = util.stdin + self.pageractive = False # shared read-only environment self.environ = encoding.environ @@ -824,6 +831,77 @@ return False return util.isatty(fh) + def pager(self, command): + """Start a pager for subsequent command output. + + Commands which produce a long stream of output should call + this function to activate the user's preferred pagination + mechanism (which may be no pager). Calling this function + precludes any future use of interactive functionality, such as + prompting the user or activating curses. + + Args: + command: The full, non-aliased name of the command. That is, "log" + not "history, "summary" not "summ", etc. + """ + if (self.pageractive + # TODO: if we want to allow HGPLAINEXCEPT=pager, + # formatted() will need some adjustment. + or not self.formatted() + or self.plain() + # TODO: expose debugger-enabled on the UI object + or '--debugger' in sys.argv): + # We only want to paginate if the ui appears to be + # interactive, the user didn't say HGPLAIN or + # HGPLAINEXCEPT=pager, and the user didn't specify --debug. + return + + # TODO: add a "system defaults" config section so this default + # of more(1) can be easily replaced with a global + # configuration file. For example, on OS X the sane default is + # less(1), not more(1), and on debian it's + # sensible-pager(1). We should probably also give the system + # default editor command similar treatment. + envpager = encoding.environ.get('PAGER', 'more') + pagercmd = self.config('pager', 'pager', envpager) + self.pageractive = True + # Preserve the formatted-ness of the UI. This is important + # because we mess with stdout, which might confuse + # auto-detection of things being formatted. + self.setconfig('ui', 'formatted', self.formatted(), 'pager') + self.setconfig('ui', 'interactive', False, 'pager') + if util.safehasattr(signal, "SIGPIPE"): + signal.signal(signal.SIGPIPE, _catchterm) + self._runpager(pagercmd) + + def _runpager(self, command): + """Actually start the pager and set up file descriptors. + + This is separate in part so that extensions (like chg) can + override how a pager is invoked. + """ + pager = subprocess.Popen(command, shell=True, bufsize=-1, + close_fds=util.closefds, stdin=subprocess.PIPE, + stdout=util.stdout, stderr=util.stderr) + + # back up original file descriptors + stdoutfd = os.dup(util.stdout.fileno()) + stderrfd = os.dup(util.stderr.fileno()) + + os.dup2(pager.stdin.fileno(), util.stdout.fileno()) + if self._isatty(util.stderr): + os.dup2(pager.stdin.fileno(), util.stderr.fileno()) + + @atexit.register + def killpager(): + if util.safehasattr(signal, "SIGINT"): + signal.signal(signal.SIGINT, signal.SIG_IGN) + # restore original fds, closing pager.stdin copies in the process + os.dup2(stdoutfd, util.stdout.fileno()) + os.dup2(stderrfd, util.stderr.fileno()) + pager.stdin.close() + pager.wait() + def interface(self, feature): """what interface to use for interactive console features?