diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1a7e64f..740d652ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fix capturing stdout on legacy Windows https://github.com/Textualize/rich/pull/2055 +- Fix capturing stdout on legacy Windows https://github.com/Textualize/rich/pull/2066 ## [12.0.0] - 2022-03-10 diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 0171f3fa2..f642279c5 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,7 +4,7 @@ """ import ctypes import sys -from typing import Any, NamedTuple, Type, cast +from typing import Any windll: Any = None if sys.platform == "win32": @@ -14,6 +14,7 @@ import time from ctypes import Structure, byref, wintypes +from typing import IO, NamedTuple, Type, cast from rich.color import ColorSystem from rich.style import Style @@ -333,7 +334,7 @@ class LegacyWindowsTerm: 15, # bright white ] - def __init__(self, file: IO[str]) -> None: + def __init__(self, file: "IO[str]") -> None: handle = GetStdHandle(STDOUT) self._handle = handle default_text = GetConsoleScreenBufferInfo(handle).wAttributes diff --git a/rich/console.py b/rich/console.py index 12406aefd..b91c84871 100644 --- a/rich/console.py +++ b/rich/console.py @@ -82,6 +82,15 @@ class NoChange: NO_CHANGE = NoChange() +try: + _STDOUT_FILENO = sys.__stdout__.fileno() +except Exception: + _STDOUT_FILENO = 1 + +try: + _STDERR_FILENO = sys.__stderr__.fileno() +except Exception: + _STDERR_FILENO = 2 CONSOLE_HTML_FORMAT = """\ @@ -1909,12 +1918,21 @@ def log( buffer_extend(line) def _check_buffer(self) -> None: - """Check if the buffer may be rendered.""" + """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) + Rendering is supported on Windows, Unix and Jupyter environments. For + legacy Windows consoles, the win32 API is called directly. + This method will also record what it renders if recording is enabled via Console.record. + """ if self.quiet: del self._buffer[:] return with self._lock: if self._buffer_index == 0: + + if self.record: + with self._record_buffer_lock: + self._record_buffer.extend(self._buffer[:]) + if self.is_jupyter: # pragma: no cover from .jupyter import display @@ -1927,18 +1945,19 @@ def _check_buffer(self) -> None: except (ValueError, io.UnsupportedOperation): file_no = -1 - legacy_windows_stdout = self.legacy_windows and file_no == 1 - if legacy_windows_stdout: + stdout_num = _STDOUT_FILENO + stderr_num = _STDERR_FILENO + is_std_stream = file_no in (stdout_num, stderr_num) + legacy_windows_std = self.legacy_windows and is_std_stream + if legacy_windows_std: from rich._win32_console import LegacyWindowsTerm from rich._windows_renderer import legacy_windows_render - with open(file_no, "w") as output_file: - legacy_windows_render( - self._buffer[:], LegacyWindowsTerm(output_file) - ) - - output_capture_enabled = bool(self._buffer_index) - if not legacy_windows_stdout or output_capture_enabled: + legacy_windows_render( + self._buffer[:], LegacyWindowsTerm(self.file) + ) + else: + # Either a non-std stream on legacy Windows, or modern Windows. text = self._render_buffer(self._buffer[:]) # https://bugs.python.org/issue37871 write = self.file.write @@ -1965,9 +1984,6 @@ def _render_buffer(self, buffer: Iterable[Segment]) -> str: append = output.append color_system = self._color_system legacy_windows = self.legacy_windows - if self.record: - with self._record_buffer_lock: - self._record_buffer.extend(buffer) not_terminal = not self.is_terminal if self.no_color and color_system: buffer = Segment.remove_color(buffer)