From 8c3eee03973693cfdebd4ba2f20437ce90e9ac4c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Mar 2022 11:02:10 +0000 Subject: [PATCH 01/18] 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 12406aef..4873f4a8 100644 --- a/rich/console.py +++ b/rich/console.py @@ -796,7 +796,7 @@ class Console: 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 @@ class Console: 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 @@ class Console: ): 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 @@ class Console: 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 @@ class Console: 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 02/18] 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 632408e0..368fb2ef 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 03/18] 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 368fb2ef..f642279c 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,7 +4,7 @@ The API that this module wraps is documented at https://docs.microsoft.com/en-us """ 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 @@ else: 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 04/18] 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 f642279c..bfe17d53 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,9 +4,8 @@ The API that this module wraps is documented at https://docs.microsoft.com/en-us """ 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 05/18] 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 bfe17d53..f642279c 100644 --- a/rich/_win32_console.py +++ b/rich/_win32_console.py @@ -4,8 +4,9 @@ The API that this module wraps is documented at https://docs.microsoft.com/en-us """ 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 06/18] Update PR number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1a7e64..740d652f 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 07/18] 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 4873f4a8..fee60c4b 100644 --- a/rich/console.py +++ b/rich/console.py @@ -2,6 +2,7 @@ import inspect import io import os import platform +import pty import sys import threading from abc import ABC, abstractmethod @@ -796,7 +797,7 @@ class Console: 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 @@ class Console: 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 @@ class Console: ): 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 @@ class Console: 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 bb1967656fe6a68a8e28f8336ade70ee1f3548e5 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 16 Mar 2022 10:55:41 +0000 Subject: [PATCH 08/18] GitHub Action to asv publish and copy to rich-benchmarks repo --- .../workflows/update_benchmark_website.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/update_benchmark_website.yml diff --git a/.github/workflows/update_benchmark_website.yml b/.github/workflows/update_benchmark_website.yml new file mode 100644 index 00000000..e6eccebb --- /dev/null +++ b/.github/workflows/update_benchmark_website.yml @@ -0,0 +1,24 @@ +on: + release: + types: [ released ] + + +jobs: + asv-publish-and-copy: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.10.2' + - run: 'pip install asv' + - run: 'asv publish' + - uses: pina/github-action-push-to-another-repository@v1.4.1 + name: 'Copy files to Textualize/rich-benchmarks repo' + env: + API_TOKEN_GITHUB: ${{ secrets.PUBLISH_ASV }} + with: + source-directory: /benchmarks/html + destination-github-username: Textualize + destination-repository-name: rich-benchmarks + user-email: darren@textualize.io From 98969ab03c9f354ab36fe8423b2c754f25834fa2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 16 Mar 2022 11:28:26 +0000 Subject: [PATCH 09/18] 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 fee60c4b..b91c8487 100644 --- a/rich/console.py +++ b/rich/console.py @@ -2,7 +2,6 @@ import inspect 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 @@ class Console: 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: From b102e9068ca7851c60c3266075df726f1dd481d6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 17 Mar 2022 14:11:26 +0000 Subject: [PATCH 10/18] revert segment optimization --- pyproject.toml | 2 +- rich/segment.py | 38 +++++++++++++------------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e94bdd5d..3fd47ce4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ jupyter = ["ipywidgets"] [tool.poetry.dev-dependencies] pytest = "^7.0.0" black = "^22.1" -mypy = "^0.931" +mypy = "^0.941" pytest-cov = "^3.0.0" attrs = "^21.4.0" types-dataclasses = "^0.6.4" diff --git a/rich/segment.py b/rich/segment.py index 8ad6c548..61457baa 100644 --- a/rich/segment.py +++ b/rich/segment.py @@ -11,6 +11,7 @@ from typing import ( Iterator, List, Optional, + NamedTuple, Sequence, Tuple, Type, @@ -58,7 +59,7 @@ ControlCode = Union[ @rich_repr() -class Segment: +class Segment(NamedTuple): """A piece of text with associated style. Segments are produced by the Console render process and are ultimately converted in to strings to be written to the terminal. @@ -71,32 +72,19 @@ class Segment: cell_length (int): The cell length of this Segment. """ - __slots__ = ["text", "style", "control", "cell_length", "_tuple"] + text: str + style: Optional[Style] = None + control: Optional[Sequence[ControlCode]] = None - def __init__( - self, - text: str = "", - style: Optional[Style] = None, - control: Optional[Sequence[ControlCode]] = None, - ): - self.text = text - self.style = style - self.control = control + @property + def cell_length(self) -> int: + """The number of terminal cells required to display self.text. - self.cell_length = 0 if self.control else cell_len(self.text) - - self._tuple = text, style, control - - def __iter__(self) -> Iterator[Any]: - return iter(self._tuple) - - def __eq__(self, other: object) -> bool: - if isinstance(other, Segment): - return self._tuple == other._tuple - return self._tuple == other - - def __hash__(self) -> int: - return hash(self._tuple) + Returns: + int: A number of cells. + """ + text, _style, control = self + return 0 if control else cell_len(text) def __rich_repr__(self) -> Result: yield self.text From 1586c3e628f7b3e87d37f87d0b03a8b1710a0790 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 17 Mar 2022 15:56:12 +0000 Subject: [PATCH 11/18] Use `reversed` instead of `[::-1]` to reverse in chop_cells --- rich/cells.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rich/cells.py b/rich/cells.py index dd4d5e9d..d7adf5a0 100644 --- a/rich/cells.py +++ b/rich/cells.py @@ -113,14 +113,12 @@ def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]: _get_character_cell_size = get_character_cell_size characters = [ (character, _get_character_cell_size(character)) for character in text - ][::-1] + ] total_size = position lines: List[List[str]] = [[]] append = lines[-1].append - pop = characters.pop - while characters: - character, size = pop() + for character, size in reversed(characters): if total_size + size > max_size: lines.append([character]) append = lines[-1].append @@ -128,6 +126,7 @@ def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]: else: total_size += size append(character) + return ["".join(line) for line in lines] From 23b266cda944be94677c1adaf21b4b76a48a55a1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 17 Mar 2022 16:04:36 +0000 Subject: [PATCH 12/18] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1a7e64..d96c6f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improve performance of cell_length https://github.com/Textualize/rich/pull/2061 +- Improve performance of chop_cells https://github.com/Textualize/rich/pull/2077 ## [12.0.1] - 2022-03-14 From 008bf87cc0c13b51a99d8e48722b9c4202731742 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 08:52:23 +0000 Subject: [PATCH 13/18] poetry lock --- poetry.lock | 76 ++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/poetry.lock b/poetry.lock index f46e5185..fb165cd0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -356,7 +356,7 @@ python-versions = "*" [[package]] name = "ipywidgets" -version = "7.6.5" +version = "7.7.0" description = "IPython HTML widgets for Jupyter" category = "main" optional = true @@ -369,7 +369,7 @@ ipython-genutils = ">=0.2.0,<0.3.0" jupyterlab-widgets = {version = ">=1.0.0", markers = "python_version >= \"3.6\""} nbformat = ">=4.2.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=3.5.0,<3.6.0" +widgetsnbextension = ">=3.6.0,<3.7.0" [package.extras] test = ["pytest (>=3.6.0)", "pytest-cov", "mock"] @@ -466,7 +466,7 @@ pygments = ">=2.4.1,<3" [[package]] name = "jupyterlab-widgets" -version = "1.0.2" +version = "1.1.0" description = "A JupyterLab extension." category = "main" optional = true @@ -490,7 +490,7 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.931" +version = "0.941" description = "Optional static typing for Python" category = "dev" optional = false @@ -505,6 +505,7 @@ typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" @@ -600,7 +601,7 @@ python-versions = "*" [[package]] name = "notebook" -version = "6.4.8" +version = "6.4.10" description = "A web-based notebook environment for interactive computing" category = "main" optional = true @@ -613,7 +614,7 @@ ipython-genutils = "*" jinja2 = "*" jupyter-client = ">=5.3.4" jupyter-core = ">=4.6.1" -nbconvert = "*" +nbconvert = ">=5" nbformat = "*" nest-asyncio = ">=1.5" prometheus-client = "*" @@ -1037,7 +1038,7 @@ python-versions = "*" [[package]] name = "widgetsnbextension" -version = "3.5.2" +version = "3.6.0" description = "IPython HTML widgets for Jupyter" category = "main" optional = true @@ -1064,7 +1065,7 @@ jupyter = ["ipywidgets"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "5ce16f2949f71818ef5552c9001615a07b6e604a6291ff34b0f5fdc29a684cdc" +content-hash = "9d010fc1dade302bb784d0607a4c23d00e89f6709572310452428d347ff8b89a" [metadata.files] appnope = [ @@ -1316,8 +1317,8 @@ ipython-genutils = [ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, ] ipywidgets = [ - {file = "ipywidgets-7.6.5-py2.py3-none-any.whl", hash = "sha256:d258f582f915c62ea91023299603be095de19afb5ee271698f88327b9fe9bf43"}, - {file = "ipywidgets-7.6.5.tar.gz", hash = "sha256:00974f7cb4d5f8d494c19810fedb9fa9b64bffd3cda7c2be23c133a1ad3c99c5"}, + {file = "ipywidgets-7.7.0-py2.py3-none-any.whl", hash = "sha256:e58ff58bc94d481e91ecb6e13a5cb96a87b6b8ade135e055603d0ca24593df38"}, + {file = "ipywidgets-7.7.0.tar.gz", hash = "sha256:ab4a5596855a88b83761921c768707d65e5847068139bc1729ddfe834703542a"}, ] jedi = [ {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, @@ -1344,8 +1345,8 @@ jupyterlab-pygments = [ {file = "jupyterlab_pygments-0.1.2.tar.gz", hash = "sha256:cfcda0873626150932f438eccf0f8bf22bfa92345b814890ab360d666b254146"}, ] jupyterlab-widgets = [ - {file = "jupyterlab_widgets-1.0.2-py3-none-any.whl", hash = "sha256:f5d9efface8ec62941173ba1cffb2edd0ecddc801c11ae2931e30b50492eb8f7"}, - {file = "jupyterlab_widgets-1.0.2.tar.gz", hash = "sha256:7885092b2b96bf189c3a705cc3c412a4472ec5e8382d0b47219a66cccae73cfa"}, + {file = "jupyterlab_widgets-1.1.0-py3-none-any.whl", hash = "sha256:c2a9bd3789f120f64d73268c066ed3b000c56bc1dda217be5cdc43e7b4ebad3f"}, + {file = "jupyterlab_widgets-1.1.0.tar.gz", hash = "sha256:d5f41bc1713795385f718d44dcba47e1e1473c6289f28a95aa6b2c0782ee372a"}, ] markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, @@ -1423,26 +1424,29 @@ mistune = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] mypy = [ - {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, - {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, - {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, - {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, - {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, - {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, - {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, - {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, - {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, - {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, - {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, - {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, - {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, - {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, - {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, - {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, - {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, - {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, - {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, - {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, + {file = "mypy-0.941-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:98f61aad0bb54f797b17da5b82f419e6ce214de0aa7e92211ebee9e40eb04276"}, + {file = "mypy-0.941-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6a8e1f63357851444940351e98fb3252956a15f2cabe3d698316d7a2d1f1f896"}, + {file = "mypy-0.941-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b30d29251dff4c59b2e5a1fa1bab91ff3e117b4658cb90f76d97702b7a2ae699"}, + {file = "mypy-0.941-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8eaf55fdf99242a1c8c792247c455565447353914023878beadb79600aac4a2a"}, + {file = "mypy-0.941-cp310-cp310-win_amd64.whl", hash = "sha256:080097eee5393fd740f32c63f9343580aaa0fb1cda0128fd859dfcf081321c3d"}, + {file = "mypy-0.941-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f79137d012ff3227866222049af534f25354c07a0d6b9a171dba9f1d6a1fdef4"}, + {file = "mypy-0.941-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e5974583a77d630a5868eee18f85ac3093caf76e018c510aeb802b9973304ce"}, + {file = "mypy-0.941-cp36-cp36m-win_amd64.whl", hash = "sha256:0dd441fbacf48e19dc0c5c42fafa72b8e1a0ba0a39309c1af9c84b9397d9b15a"}, + {file = "mypy-0.941-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d3bcbe146247997e03bf030122000998b076b3ac6925b0b6563f46d1ce39b50"}, + {file = "mypy-0.941-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bada0cf7b6965627954b3a128903a87cac79a79ccd83b6104912e723ef16c7b"}, + {file = "mypy-0.941-cp37-cp37m-win_amd64.whl", hash = "sha256:eea10982b798ff0ccc3b9e7e42628f932f552c5845066970e67cd6858655d52c"}, + {file = "mypy-0.941-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:108f3c7e14a038cf097d2444fa0155462362c6316e3ecb2d70f6dd99cd36084d"}, + {file = "mypy-0.941-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d61b73c01fc1de799226963f2639af831307fe1556b04b7c25e2b6c267a3bc76"}, + {file = "mypy-0.941-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:42c216a33d2bdba08098acaf5bae65b0c8196afeb535ef4b870919a788a27259"}, + {file = "mypy-0.941-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fc5ecff5a3bbfbe20091b1cad82815507f5ae9c380a3a9bf40f740c70ce30a9b"}, + {file = "mypy-0.941-cp38-cp38-win_amd64.whl", hash = "sha256:bf446223b2e0e4f0a4792938e8d885e8a896834aded5f51be5c3c69566495540"}, + {file = "mypy-0.941-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:745071762f32f65e77de6df699366d707fad6c132a660d1342077cbf671ef589"}, + {file = "mypy-0.941-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:465a6ce9ca6268cadfbc27a2a94ddf0412568a6b27640ced229270be4f5d394d"}, + {file = "mypy-0.941-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d051ce0946521eba48e19b25f27f98e5ce4dbc91fff296de76240c46b4464df0"}, + {file = "mypy-0.941-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:818cfc51c25a5dbfd0705f3ac1919fff6971eb0c02e6f1a1f6a017a42405a7c0"}, + {file = "mypy-0.941-cp39-cp39-win_amd64.whl", hash = "sha256:b2ce2788df0c066c2ff4ba7190fa84f18937527c477247e926abeb9b1168b8cc"}, + {file = "mypy-0.941-py3-none-any.whl", hash = "sha256:3cf77f138efb31727ee7197bc824c9d6d7039204ed96756cc0f9ca7d8e8fc2a4"}, + {file = "mypy-0.941.tar.gz", hash = "sha256:cbcc691d8b507d54cb2b8521f0a2a3d4daa477f62fe77f0abba41e5febb377b7"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1469,8 +1473,8 @@ nodeenv = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] notebook = [ - {file = "notebook-6.4.8-py3-none-any.whl", hash = "sha256:3e702fcc54b8ae597533c3864793b7a1e971dec9e112f67235828d8a798fd654"}, - {file = "notebook-6.4.8.tar.gz", hash = "sha256:1e985c9dc6f678bdfffb9dc657306b5469bfa62d73e03f74e8defbf76d284312"}, + {file = "notebook-6.4.10-py3-none-any.whl", hash = "sha256:49cead814bff0945fcb2ee07579259418672ac175d3dc3d8102a4b0a656ed4df"}, + {file = "notebook-6.4.10.tar.gz", hash = "sha256:2408a76bc6289283a8eecfca67e298ec83c67db51a4c2e1b713dd180bb39e90e"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -1794,8 +1798,8 @@ webencodings = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] widgetsnbextension = [ - {file = "widgetsnbextension-3.5.2-py2.py3-none-any.whl", hash = "sha256:763a9fdc836d141fa080005a886d63f66f73d56dba1fb5961afc239c77708569"}, - {file = "widgetsnbextension-3.5.2.tar.gz", hash = "sha256:e0731a60ba540cd19bbbefe771a9076dcd2dde90713a8f87f27f53f2d1db7727"}, + {file = "widgetsnbextension-3.6.0-py2.py3-none-any.whl", hash = "sha256:4fd321cad39fdcf8a8e248a657202d42917ada8e8ed5dd3f60f073e0d54ceabd"}, + {file = "widgetsnbextension-3.6.0.tar.gz", hash = "sha256:e84a7a9fcb9baf3d57106e184a7389a8f8eb935bf741a5eb9d60aa18cc029a80"}, ] zipp = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, From 7291abf69e4f9d0940e7b938b625e9f437fe64ca Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 09:04:38 +0000 Subject: [PATCH 14/18] sort order --- rich/ansi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rich/ansi.py b/rich/ansi.py index 92e4772e..2023fbac 100644 --- a/rich/ansi.py +++ b/rich/ansi.py @@ -1,5 +1,5 @@ -from contextlib import suppress import re +from contextlib import suppress from typing import Iterable, NamedTuple from .color import Color @@ -199,9 +199,9 @@ class AnsiDecoder: if __name__ == "__main__": # pragma: no cover - import pty import io import os + import pty import sys decoder = AnsiDecoder() @@ -213,7 +213,7 @@ if __name__ == "__main__": # pragma: no cover stdout.write(data) return data - pty.spawn(sys.argv[1:], read) + pty.spawn(sys.argv[1:], read) # type: ignore from .console import Console From 8d2dbca161df5fbf241cd63cdaa1334a1b3fb9c4 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 16:34:48 +0000 Subject: [PATCH 15/18] typing issue --- rich/ansi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rich/ansi.py b/rich/ansi.py index 2023fbac..cff1a049 100644 --- a/rich/ansi.py +++ b/rich/ansi.py @@ -198,7 +198,7 @@ class AnsiDecoder: return text -if __name__ == "__main__": # pragma: no cover +if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover import io import os import pty @@ -213,7 +213,7 @@ if __name__ == "__main__": # pragma: no cover stdout.write(data) return data - pty.spawn(sys.argv[1:], read) # type: ignore + pty.spawn(sys.argv[1:], read) from .console import Console From cd14f454dd14b45cb96e9a55fa1f53dc03f1f7bc Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 16:36:58 +0000 Subject: [PATCH 16/18] forgotten import --- rich/ansi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rich/ansi.py b/rich/ansi.py index cff1a049..5fb7e9ea 100644 --- a/rich/ansi.py +++ b/rich/ansi.py @@ -1,4 +1,5 @@ import re +import sys from contextlib import suppress from typing import Iterable, NamedTuple From 3415272059952cc86114bf5a2148c3e932ae4ff5 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 17:09:03 +0000 Subject: [PATCH 17/18] streamline legacy windows logic --- rich/_windows_renderer.py | 4 +--- rich/console.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rich/_windows_renderer.py b/rich/_windows_renderer.py index e9debe30..514c4511 100644 --- a/rich/_windows_renderer.py +++ b/rich/_windows_renderer.py @@ -11,9 +11,7 @@ def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls. term (LegacyWindowsTerm): Used to call the Windows Console API. """ - for segment in buffer: - text, style, control = segment - + for text, style, control in buffer: if not control: if style: term.write_styled(text, style) diff --git a/rich/console.py b/rich/console.py index b91c8487..6a0b3e90 100644 --- a/rich/console.py +++ b/rich/console.py @@ -92,6 +92,8 @@ try: except Exception: _STDERR_FILENO = 2 +_STD_STREAMS = (_STDOUT_FILENO, _STDERR_FILENO) + CONSOLE_HTML_FORMAT = """\ @@ -1940,16 +1942,16 @@ class Console: del self._buffer[:] else: if WINDOWS: - try: - file_no = self.file.fileno() - except (ValueError, io.UnsupportedOperation): - file_no = -1 + use_legacy_windows_render = False + if self.legacy_windows: + try: + use_legacy_windows_render = ( + self.file.fileno() in _STD_STREAMS + ) + except (ValueError, io.UnsupportedOperation): + pass - 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: + if use_legacy_windows_render: from rich._win32_console import LegacyWindowsTerm from rich._windows_renderer import legacy_windows_render From 0daccb07684760c86a5583ee98bb94a406837992 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 18 Mar 2022 17:17:10 +0000 Subject: [PATCH 18/18] fix uuid highlighter --- rich/highlighter.py | 2 +- tests/test_highlighter.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rich/highlighter.py b/rich/highlighter.py index b95673cc..7bee4167 100644 --- a/rich/highlighter.py +++ b/rich/highlighter.py @@ -90,13 +90,13 @@ class ReprHighlighter(RegexHighlighter): r"(?P([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})", r"(?P(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})", r"(?P(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})", + r"(?P[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})", r"(?P[\w.]*?)\(", r"\b(?PTrue)\b|\b(?PFalse)\b|\b(?PNone)\b", r"(?P\.\.\.)", r"(?P(?\B(/[-\w._+]+)*\/)(?P[-\w._+]*)?", r"(?b?'''.*?(?[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})", r"(?P(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)", ), ] diff --git a/tests/test_highlighter.py b/tests/test_highlighter.py index a34334e3..930b340e 100644 --- a/tests/test_highlighter.py +++ b/tests/test_highlighter.py @@ -83,6 +83,7 @@ highlight_tests = [ ('"""hello"""', [Span(0, 11, "repr.str")]), ("\\'foo'", []), ("it's no 'string'", [Span(8, 16, "repr.str")]), + ("78351748-9b32-4e08-ad3e-7e9ff124d541", [Span(0, 36, "repr.uuid")]), ]