Submitter | Augie Fackler |
---|---|
Date | Feb. 16, 2017, 2:12 a.m. |
Message ID | <675643abfdb6adbdfd8b.1487211153@imladris.local> |
Download | mbox | patch |
Permalink | /patch/18535/ |
State | Superseded |
Headers | show |
Comments
> On Feb 15, 2017, at 9:12 PM, Augie Fackler <raf@durin42.com> wrote: > > # HG changeset patch > # User Augie Fackler <augie@google.com> > # Date 1487198871 18000 > # Wed Feb 15 17:47:51 2017 -0500 > # Node ID 675643abfdb6adbdfd8bddfbc263701c9caca539 > # Parent e5363cb96233861fc99f7e9b85d7884d3121558c > pager: move pager-initiating code into core These 9 patches represent most of the functionally interesting bits of my pager-in-core series. The rest of the stack is visible at https://hg.durin42.com/hg-wip/graph/pager - the majority of the patches beyond this point are one-line “turn on pager” commits. I expect to send the remaining 21 patches in two batches: one wave of 19 trivial “turn it on” patches, and then 2 patches that rearrange some docs and mark the pager extension as deprecated. > > 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 --git a/hgext/pager.py b/hgext/pager.py > --- a/hgext/pager.py > +++ b/hgext/pager.py > @@ -60,19 +60,11 @@ you can use --pager=<value>:: > ''' > 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 @@ from mercurial import ( > # 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 @@ def uisetup(ui): > 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 --git a/mercurial/ui.py b/mercurial/ui.py > --- a/mercurial/ui.py > +++ b/mercurial/ui.py > @@ -7,13 +7,16 @@ > > from __future__ import absolute_import > > +import atexit > import contextlib > import errno > import getpass > import inspect > import os > import re > +import signal > import socket > +import subprocess > import sys > import tempfile > import traceback > @@ -143,6 +146,7 @@ class ui(object): > 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() > @@ -159,6 +163,7 @@ class ui(object): > self.fout = util.stdout > self.ferr = util.stderr > self.fin = util.stdin > + self.pageractive = False > > # shared read-only environment > self.environ = encoding.environ > @@ -792,6 +797,75 @@ class ui(object): > 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') > + 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? > > _______________________________________________ > Mercurial-devel mailing list > Mercurial-devel@mercurial-scm.org > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
On 16/02/2017 02:12, Augie Fackler wrote: > # HG changeset patch > # User Augie Fackler <augie@google.com> > # Date 1487198871 18000 > # Wed Feb 15 17:47:51 2017 -0500 > # Node ID 675643abfdb6adbdfd8bddfbc263701c9caca539 > # Parent e5363cb96233861fc99f7e9b85d7884d3121558c > 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 --git a/hgext/pager.py b/hgext/pager.py > --- a/hgext/pager.py > +++ b/hgext/pager.py > @@ -60,19 +60,11 @@ you can use --pager=<value>:: > ''' > 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 @@ from mercurial import ( > # 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 > - This little helper... > 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 @@ def uisetup(ui): > 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) ..and this change to signal handling are important. Without them, if you do `hg log -r 'all()'` on a large repository, then quit the pager, Mercurial will continue to crunch away in the background, generating stdout that goes to the (now-deceased) pager. > - 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 --git a/mercurial/ui.py b/mercurial/ui.py > --- a/mercurial/ui.py > +++ b/mercurial/ui.py > @@ -7,13 +7,16 @@ > > from __future__ import absolute_import > > +import atexit > import contextlib > import errno > import getpass > import inspect > import os > import re > +import signal > import socket > +import subprocess > import sys > import tempfile > import traceback > @@ -143,6 +146,7 @@ class ui(object): > 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() > @@ -159,6 +163,7 @@ class ui(object): > self.fout = util.stdout > self.ferr = util.stderr > self.fin = util.stdin > + self.pageractive = False > > # shared read-only environment > self.environ = encoding.environ > @@ -792,6 +797,75 @@ class ui(object): > 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') The equivalent signal handling change belongs here. > + 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? > > _______________________________________________ > Mercurial-devel mailing list > Mercurial-devel@mercurial-scm.org > https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=DwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=ZiiKlZHpcE6FnMZhBU6-nzVLClXOgHOARg0HpwT43yU&s=7H11BAlHtU6CGYWE4DAym4-8Q81xpiKmHHJ2JBpLKko&e= >
Patch
diff --git a/hgext/pager.py b/hgext/pager.py --- a/hgext/pager.py +++ b/hgext/pager.py @@ -60,19 +60,11 @@ you can use --pager=<value>:: ''' 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 @@ from mercurial import ( # 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 @@ def uisetup(ui): 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 --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -7,13 +7,16 @@ from __future__ import absolute_import +import atexit import contextlib import errno import getpass import inspect import os import re +import signal import socket +import subprocess import sys import tempfile import traceback @@ -143,6 +146,7 @@ class ui(object): 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() @@ -159,6 +163,7 @@ class ui(object): self.fout = util.stdout self.ferr = util.stderr self.fin = util.stdin + self.pageractive = False # shared read-only environment self.environ = encoding.environ @@ -792,6 +797,75 @@ class ui(object): 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') + 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?