88
99import collections .abc as cabc
1010import contextlib
11+ import io
1112import math
1213import os
1314import sys
@@ -360,7 +361,20 @@ def generator(self) -> cabc.Iterator[V]:
360361 self .render_progress ()
361362
362363
363- def _pager_contextmanager (color : bool | None = None ):
364+ class MaybeStripAnsi (io .TextIOWrapper ):
365+ def __init__ (self , stream : t .IO [bytes ], * , color : bool , ** kwargs : t .Any ):
366+ super ().__init__ (stream , ** kwargs )
367+ self .color = color
368+
369+ def write (self , text : str ) -> int :
370+ if not self .color :
371+ text = strip_ansi (text )
372+ return super ().write (text )
373+
374+
375+ def _pager_contextmanager (
376+ color : bool | None = None ,
377+ ) -> t .ContextManager [t .Tuple [t .BinaryIO , str , bool ]]:
364378 """Decide what method to use for paging through text."""
365379 stdout = _default_text_stdout ()
366380
@@ -395,17 +409,26 @@ def _pager_contextmanager(color: bool | None = None):
395409 os .unlink (filename )
396410
397411
398- def pager (generator : cabc .Iterable [str ], color : bool | None = None ):
399- """Given an iterable of text, write it all to an output pager."""
400- with _pager_contextmanager (color = color ) as (pager_file , encoding , color ):
401- for text in generator :
402- if not color :
403- text = strip_ansi (text )
404- pager_file .write (text .encode (encoding , "replace" ))
412+ @contextlib .contextmanager
413+ def get_pager_file (color : bool | None = None ) -> t .Generator [t .IO , None , None ]:
414+ """Context manager.
415+ Yields a writable file-like object which can be used as an output pager.
416+ .. versionadded:: 8.2
417+ :param color: controls if the pager supports ANSI colors or not. The
418+ default is autodetection.
419+ """
420+ with _pager_contextmanager (color = color ) as (stream , encoding , color ):
421+ if not getattr (stream , "encoding" , None ):
422+ # wrap in a text stream
423+ stream = MaybeStripAnsi (stream , color = color , encoding = encoding )
424+ yield stream
425+ stream .flush ()
405426
406427
407428@contextlib .contextmanager
408- def _pipepager (cmd : str , color : bool | None = None ):
429+ def _pipepager (
430+ cmd : str , color : bool | None
431+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
409432 """Page through text by feeding it to another program. Invoking a
410433 pager through this might support colors.
411434 """
@@ -424,15 +447,17 @@ def _pipepager(cmd: str, color: bool | None = None):
424447 elif "r" in less_flags or "R" in less_flags :
425448 color = True
426449
450+ if color is None :
451+ color = False
452+
427453 c = subprocess .Popen (cmd , shell = True , stdin = subprocess .PIPE , env = env )
428454 stdin = t .cast (t .BinaryIO , c .stdin )
429455 encoding = get_best_encoding (stdin )
430- try :
431- yield stdin , encoding , color
432- except (OSError , KeyboardInterrupt ):
433- pass
434- else :
435- stdin .close ()
456+ with stdin :
457+ try :
458+ yield stdin , encoding , color
459+ except (OSError , KeyboardInterrupt ):
460+ pass
436461
437462 # Less doesn't respect ^C, but catches it for its own UI purposes (aborting
438463 # search or other commands inside less).
@@ -452,7 +477,9 @@ def _pipepager(cmd: str, color: bool | None = None):
452477
453478
454479@contextlib .contextmanager
455- def _tempfilepager (cmd : str , color : bool | None = None ):
480+ def _tempfilepager (
481+ cmd : str , color : bool | None = None
482+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
456483 """Page through text by invoking a program on a temporary file."""
457484 import tempfile
458485
@@ -464,10 +491,12 @@ def _tempfilepager(cmd: str, color: bool | None = None):
464491
465492
466493@contextlib .contextmanager
467- def _nullpager (stream : t .TextIO , color : bool | None = None ):
494+ def _nullpager (
495+ stream : t .TextIO , color : bool | None = None
496+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
468497 """Simply print unformatted text. This is the ultimate fallback."""
469498 encoding = get_best_encoding (stream )
470- return stream , encoding , color
499+ yield stream , encoding , color
471500
472501
473502class Editor :
@@ -608,23 +637,23 @@ def _unquote_file(url: str) -> str:
608637 wait_str = "-w" if wait else ""
609638 args = f'cygstart { wait_str } "{ url } "'
610639 return os .system (args )
611-
612- try :
613- if locate :
614- url = os .path .dirname (_unquote_file (url )) or "."
615- else :
616- url = _unquote_file (url )
617- c = subprocess .Popen (["xdg-open" , url ])
618- if wait :
619- return c .wait ()
620- return 0
621- except OSError :
622- if url .startswith (("http://" , "https://" )) and not locate and not wait :
623- import webbrowser
624-
625- webbrowser .open (url )
640+ else :
641+ try :
642+ if locate :
643+ url = os .path .dirname (_unquote_file (url )) or "."
644+ else :
645+ url = _unquote_file (url )
646+ c = subprocess .Popen (["xdg-open" , url ])
647+ if wait :
648+ return c .wait ()
626649 return 0
627- return 1
650+ except OSError :
651+ if url .startswith (("http://" , "https://" )) and not locate and not wait :
652+ import webbrowser
653+
654+ webbrowser .open (url )
655+ return 0
656+ return 1
628657
629658
630659def _translate_ch_to_exc (ch : str ) -> None :
0 commit comments