From 8c3eee03973693cfdebd4ba2f20437ce90e9ac4c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:02:10 +0000 Subject: [PATCH 1/8] Handle record=True on legacy mode Windows consoles --- rich/console.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/rich/console.py b/rich/console.py index 12406aefd..4873f4a80 100644 --- a/rich/console.py +++ b/rich/console.py @@ -796,7 +796,7 @@ def _enter_buffer(self) -> None: def _exit_buffer(self) -> None: """Leave buffer context, and render content if required.""" self._buffer_index -= 1 - self._check_buffer() + self._check_and_render_buffer() def set_live(self, live: "Live") -> None: """Set Live instance. Used by Live context manager. @@ -1754,7 +1754,7 @@ def update_screen_lines( screen_update = ScreenUpdate(lines, x, y) segments = self.render(screen_update) self._buffer.extend(segments) - self._check_buffer() + self._check_and_render_buffer() def print_exception( self, @@ -1908,13 +1908,18 @@ def log( ): buffer_extend(line) - def _check_buffer(self) -> None: - """Check if the buffer may be rendered.""" + def _check_and_render_buffer(self) -> None: + """Check if the buffer may be rendered. Render it if it can.""" 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 @@ -1924,21 +1929,24 @@ def _check_buffer(self) -> None: if WINDOWS: try: file_no = self.file.fileno() + stdout_num = sys.stdout.fileno() + stderr_num = sys.stderr.fileno() except (ValueError, io.UnsupportedOperation): file_no = -1 + stdout_num = 1 + stderr_num = 2 - legacy_windows_stdout = self.legacy_windows and file_no == 1 - if legacy_windows_stdout: + 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 +1973,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) From 5abdc870bdd3514fced7ac5f2f78e6458df813bb Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:19:03 +0000 Subject: [PATCH 2/8] Add quotes around type IO[str] --- rich/_win32_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 632408e02..368fb2ef8 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -333,7 +333,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 From fa52c0dee9793df1c1e5da63f51f8e7d77b35808 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:41:25 +0000 Subject: [PATCH 3/8] Move typing imports below if block --- rich/_win32_console.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index 368fb2ef8..f642279c5 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,7 +4,7 @@ """ import ctypes import sys -from typing import IO, 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 From 665bb3360ab7bb64865c2ed884efdd2eb72ed473 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:49:56 +0000 Subject: [PATCH 4/8] Type windll as object instead of Any --- rich/_win32_console.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index f642279c5..bfe17d53d 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,9 +4,8 @@ """ import ctypes import sys -from typing import Any -windll: Any = None +windll: object = None if sys.platform == "win32": windll = ctypes.LibraryLoader(ctypes.WinDLL) else: From dff2f5cef5ccfa19f9afc35fb5132e1e9b5cc7a1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:56:17 +0000 Subject: [PATCH 5/8] Use Any instead of object --- rich/_win32_console.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rich/_win32_console.py b/rich/_win32_console.py index bfe17d53d..f642279c5 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,8 +4,9 @@ """ import ctypes import sys +from typing import Any -windll: object = None +windll: Any = None if sys.platform == "win32": windll = ctypes.LibraryLoader(ctypes.WinDLL) else: From 9cf9279982804a5d6e1b50b6c6567c3df258cb98 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 13:02:33 +0000 Subject: [PATCH 6/8] Update PR number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 1d61867e78359159b911d21f23c276e789633e9d Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 16 Mar 2022 09:46:26 +0000 Subject: [PATCH 7/8] Undo renaming method, improve docstring --- rich/console.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rich/console.py b/rich/console.py index 4873f4a80..fee60c4b7 100644 --- a/rich/console.py +++ b/rich/console.py @@ -2,6 +2,7 @@ import io import os import platform +import pty import sys import threading from abc import ABC, abstractmethod @@ -796,7 +797,7 @@ def _enter_buffer(self) -> None: def _exit_buffer(self) -> None: """Leave buffer context, and render content if required.""" self._buffer_index -= 1 - self._check_and_render_buffer() + self._check_buffer() def set_live(self, live: "Live") -> None: """Set Live instance. Used by Live context manager. @@ -1754,7 +1755,7 @@ def update_screen_lines( screen_update = ScreenUpdate(lines, x, y) segments = self.render(screen_update) self._buffer.extend(segments) - self._check_and_render_buffer() + self._check_buffer() def print_exception( self, @@ -1908,8 +1909,12 @@ def log( ): buffer_extend(line) - def _check_and_render_buffer(self) -> None: - """Check if the buffer may be rendered. Render it if it can.""" + def _check_buffer(self) -> None: + """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 @@ -1929,13 +1934,11 @@ def _check_and_render_buffer(self) -> None: if WINDOWS: try: file_no = self.file.fileno() - stdout_num = sys.stdout.fileno() - stderr_num = sys.stderr.fileno() except (ValueError, io.UnsupportedOperation): file_no = -1 - stdout_num = 1 - stderr_num = 2 + stdout_num = pty.STDOUT_FILENO + stderr_num = pty.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 98969ab03c9f354ab36fe8423b2c754f25834fa2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 16 Mar 2022 11:28:26 +0000 Subject: [PATCH 8/8] Get filenos of original stdout and stderr in console --- rich/console.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rich/console.py b/rich/console.py index fee60c4b7..b91c84871 100644 --- a/rich/console.py +++ b/rich/console.py @@ -2,7 +2,6 @@ import io import os import platform -import pty import sys import threading from abc import ABC, abstractmethod @@ -83,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 = """\ @@ -1937,8 +1945,8 @@ def _check_buffer(self) -> None: except (ValueError, io.UnsupportedOperation): file_no = -1 - stdout_num = pty.STDOUT_FILENO - stderr_num = pty.STDERR_FILENO + 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: