Patchwork [1,of,9,pager] pager: move pager-initiating code into core

login
register
mail settings
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

Augie Fackler - Feb. 16, 2017, 2:12 a.m.
# 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.
Augie Fackler - Feb. 16, 2017, 2:17 a.m.
> 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
Simon Farnsworth - Feb. 16, 2017, 10:21 a.m.
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?