From 5b8c5e9db8ccdf453e19d08f441136e16f4588d6 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 8 Jul 2021 16:21:42 +0200 Subject: [PATCH] Handle I/O operation on closed file. Bugfix for ptpython/Xonsh. This handles the situation where `sys.stdin` gets closed, by running for instance `sys.stdin.close()`. --- prompt_toolkit/input/vt100.py | 41 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/prompt_toolkit/input/vt100.py b/prompt_toolkit/input/vt100.py index ceb8e4604..1171b592f 100644 --- a/prompt_toolkit/input/vt100.py +++ b/prompt_toolkit/input/vt100.py @@ -18,6 +18,7 @@ ) from ..key_binding import KeyPress +from ..utils import DummyContext from .base import Input from .posix_utils import PosixStdinReader from .vt100_parser import Vt100Parser @@ -44,7 +45,9 @@ def __init__(self, stdin: TextIO) -> None: # (Idle reports stdin to be a TTY, but fileno() is not implemented.) try: # This should not raise, but can return 0. - stdin.fileno() + fileno = stdin.fileno() + except ValueError: + raise EOFError # stdin closed? except io.UnsupportedOperation as e: if "idlelib.run" in sys.modules: raise io.UnsupportedOperation( @@ -72,10 +75,10 @@ def __init__(self, stdin: TextIO) -> None: # Create a backup of the fileno(). We want this to work even if the # underlying file is closed, so that `typeahead_hash()` keeps working. - self._fileno = stdin.fileno() + self._fileno = fileno self._buffer: List[KeyPress] = [] # Buffer to collect the Key objects. - self.stdin_reader = PosixStdinReader(self._fileno, encoding=stdin.encoding) + self.stdin_reader = PosixStdinReader(fileno, encoding=stdin.encoding) self.vt100_parser = Vt100Parser( lambda key_press: self._buffer.append(key_press) ) @@ -123,16 +126,40 @@ def flush_keys(self) -> List[KeyPress]: @property def closed(self) -> bool: - return self.stdin_reader.closed + # See: https://github.com/prompt-toolkit/ptpython/issues/463 + # For ptpython, if the user does `sys.stdin.close()`, then + # `stdin_reader` will not yet be closed, but `stdin` will be closed. + # After this, `stdin.fileno()` and `stdin.read()` will both raise + # `ValueError`. + return self.stdin.closed or self.stdin_reader.closed def raw_mode(self) -> ContextManager[None]: - return raw_mode(self.stdin.fileno()) + try: + fileno = self.stdin.fileno() + except ValueError: + # Stdin closed? + return DummyContext() + + return raw_mode(fileno) def cooked_mode(self) -> ContextManager[None]: - return cooked_mode(self.stdin.fileno()) + try: + fileno = self.stdin.fileno() + except ValueError: + # Stdin closed? + return DummyContext() + + return cooked_mode(fileno) def fileno(self) -> int: - return self.stdin.fileno() + """ + Return file descriptor. + Raises `EOFError` when the input is closed. + """ + try: + return self.stdin.fileno() + except ValueError: + raise EOFError def typeahead_hash(self) -> str: return "fd-%s" % (self._fileno,)