From 2def1c4e811e10bd09aaee1086cca9c36e98701a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 15 Nov 2023 12:34:17 +0000 Subject: [PATCH 001/150] Add soft_wrap reactive to TextArea --- src/textual/widgets/_text_area.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 9880c78236..4017c86c39 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -216,6 +216,9 @@ class TextArea(ScrollView, can_focus=True): The text selected in the document is available via the `TextArea.selected_text` property. """ + soft_wrap: Reactive[bool] = reactive(False) + """Enable or disable soft wrapping.""" + show_line_numbers: Reactive[bool] = reactive(True) """True to show the line number column on the left edge, otherwise False. @@ -275,6 +278,7 @@ def __init__( *, language: str | None = None, theme: str | None = None, + soft_wrap: bool = False, name: str | None = None, id: str | None = None, classes: str | None = None, @@ -340,6 +344,9 @@ def __init__( self.theme = theme + self.soft_wrap = soft_wrap + """Enable or disable soft wrapping.""" + @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. From d2b3e7a79f7339d891dbe272ab32d4c18e9c71d4 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 15 Nov 2023 14:00:17 +0000 Subject: [PATCH 002/150] Remove redundant lines --- src/textual/widgets/_text_area.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 4017c86c39..b77cc81ec7 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -58,16 +58,12 @@ class ThemeDoesNotExist(Exception): This means a theme which is not builtin, or has not been registered. """ - pass - class LanguageDoesNotExist(Exception): """Raised when the user tries to use a language which does not exist. This means a language which is not builtin, or has not been registered. """ - pass - @dataclass class TextAreaLanguage: From 0a61a41c4fd01e00f0382130777981e85c55e519 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 16 Nov 2023 15:19:27 +0000 Subject: [PATCH 003/150] Add notes, update lockfile for new Rich, skeleton of WrappedDocumentView --- poetry.lock | 282 +++++++++++++++------- src/textual/document/_document.py | 7 + src/textual/document/_wrapped_document.py | 129 ++++++++++ src/textual/widgets/_text_area.py | 2 + 4 files changed, 337 insertions(+), 83 deletions(-) create mode 100644 src/textual/document/_wrapped_document.py diff --git a/poetry.lock b/poetry.lock index 78ca3c1d83..3c33f0fde5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.6" description = "Async http client/server framework (asyncio)" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -114,6 +115,7 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -128,6 +130,7 @@ frozenlist = ">=1.1.0" name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -150,6 +153,7 @@ trio = ["trio (<0.22)"] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -164,6 +168,7 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} name = "asynctest" version = "0.13.0" description = "Enhance the standard unittest package with features for testing asyncio libraries" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -175,6 +180,7 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -196,6 +202,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.13.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -214,6 +221,7 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -264,6 +272,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cached-property" version = "1.5.2" description = "A decorator for caching properties in classes." +category = "dev" optional = false python-versions = "*" files = [ @@ -275,6 +284,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -286,6 +296,7 @@ files = [ name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -297,6 +308,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -396,6 +408,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -411,6 +424,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -422,6 +436,7 @@ files = [ name = "colored" version = "1.4.4" description = "Simple library for color and formatting to terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -432,6 +447,7 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -504,6 +520,7 @@ toml = ["tomli"] name = "distlib" version = "0.3.7" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -515,6 +532,7 @@ files = [ name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -529,6 +547,7 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.2" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -544,6 +563,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -627,6 +647,7 @@ files = [ name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." +category = "dev" optional = false python-versions = "*" files = [ @@ -644,6 +665,7 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "gitdb" version = "4.0.11" description = "Git Object Database" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -658,6 +680,7 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.40" description = "GitPython is a Python library used to interact with Git repositories" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -676,6 +699,7 @@ test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre name = "griffe" version = "0.30.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -691,6 +715,7 @@ colorama = ">=0.4" name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -705,6 +730,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} name = "httpcore" version = "0.16.3" description = "A minimal low-level HTTP client." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -716,16 +742,17 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = "==1.*" +sniffio = ">=1.0.0,<2.0.0" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "httpx" version = "0.23.3" description = "The next generation HTTP client." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -741,14 +768,15 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "identify" version = "2.5.24" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -763,6 +791,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -774,6 +803,7 @@ files = [ name = "importlib-metadata" version = "6.7.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -794,6 +824,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -805,6 +836,7 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -822,6 +854,7 @@ i18n = ["Babel (>=2.7)"] name = "linkify-it-py" version = "2.0.2" description = "Links recognition library with FULL unicode support." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -842,6 +875,7 @@ test = ["coverage", "pytest", "pytest-cov"] name = "markdown" version = "3.4.4" description = "Python implementation of John Gruber's Markdown." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -860,6 +894,7 @@ testing = ["coverage", "pyyaml"] name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -887,6 +922,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -946,6 +982,7 @@ files = [ name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -965,6 +1002,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -976,6 +1014,7 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -987,6 +1026,7 @@ files = [ name = "mkdocs" version = "1.5.3" description = "Project documentation with Markdown." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1019,6 +1059,7 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp name = "mkdocs-autorefs" version = "0.4.1" description = "Automatically link across pages in MkDocs." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1034,6 +1075,7 @@ mkdocs = ">=1.1" name = "mkdocs-exclude" version = "1.0.2" description = "A mkdocs plugin that lets you exclude files or trees." +category = "dev" optional = false python-versions = "*" files = [ @@ -1047,6 +1089,7 @@ mkdocs = "*" name = "mkdocs-material" version = "9.2.7" description = "Documentation that simply works" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1071,6 +1114,7 @@ requests = ">=2.26,<3.0" name = "mkdocs-material-extensions" version = "1.2" description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1082,6 +1126,7 @@ files = [ name = "mkdocs-rss-plugin" version = "1.5.0" description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1092,17 +1137,18 @@ files = [ [package.dependencies] GitPython = ">=3.1,<3.2" mkdocs = ">=1.1,<2" -pytz = {version = "==2022.*", markers = "python_version < \"3.9\""} -tzdata = {version = "==2022.*", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} +pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""} +tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} [package.extras] -dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (==4.0.*)", "validator-collection (>=1.5,<1.6)"] -doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (==0.5.*)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] +dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"] +doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] [[package]] name = "mkdocstrings" version = "0.20.0" description = "Automatic documentation from sources, for MkDocs." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1128,6 +1174,7 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] name = "mkdocstrings-python" version = "0.10.1" description = "A Python handler for mkdocstrings." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1143,6 +1190,7 @@ mkdocstrings = ">=0.20" name = "msgpack" version = "1.0.5" description = "MessagePack serializer" +category = "dev" optional = false python-versions = "*" files = [ @@ -1215,6 +1263,7 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1298,6 +1347,7 @@ files = [ name = "mypy" version = "1.4.1" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1345,6 +1395,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1356,6 +1407,7 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1370,6 +1422,7 @@ setuptools = "*" name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1381,6 +1434,7 @@ files = [ name = "paginate" version = "0.5.6" description = "Divides large result sets into pages for easier browsing" +category = "dev" optional = false python-versions = "*" files = [ @@ -1391,6 +1445,7 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1402,6 +1457,7 @@ files = [ name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1420,6 +1476,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1438,6 +1495,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1457,6 +1515,7 @@ virtualenv = ">=20.10.0" name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1471,6 +1530,7 @@ plugins = ["importlib-metadata"] name = "pymdown-extensions" version = "10.2.1" description = "Extension pack for Python Markdown." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1489,6 +1549,7 @@ extra = ["pygments (>=2.12)"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1512,6 +1573,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-aiohttp" version = "1.0.5" description = "Pytest plugin for aiohttp support" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1531,6 +1593,7 @@ testing = ["coverage (==6.2)", "mypy (==0.931)"] name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1550,6 +1613,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "2.12.1" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1569,6 +1633,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-textual-snapshot" version = "0.4.0" description = "Snapshot testing for Textual apps" +category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -1587,6 +1652,7 @@ textual = ">=0.28.0" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1601,6 +1667,7 @@ six = ">=1.5" name = "pytz" version = "2022.7.1" description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" files = [ @@ -1612,6 +1679,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1661,6 +1729,7 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1675,6 +1744,7 @@ pyyaml = "*" name = "regex" version = "2022.10.31" description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1772,6 +1842,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1793,6 +1864,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rfc3986" version = "1.5.0" description = "Validating URI References per RFC 3986" +category = "dev" optional = false python-versions = "*" files = [ @@ -1808,13 +1880,14 @@ idna2008 = ["idna"] [[package]] name = "rich" -version = "13.6.0" +version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, - {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, ] [package.dependencies] @@ -1827,24 +1900,26 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1856,6 +1931,7 @@ files = [ name = "smmap" version = "5.0.1" description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1867,6 +1943,7 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1878,6 +1955,7 @@ files = [ name = "syrupy" version = "3.0.6" description = "Pytest Snapshot Test Utility" +category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -1893,6 +1971,7 @@ pytest = ">=5.1.0,<8.0.0" name = "textual-dev" version = "1.2.1" description = "Development tools for working with Textual" +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1911,6 +1990,7 @@ typing-extensions = ">=4.4.0,<5.0.0" name = "time-machine" version = "2.10.0" description = "Travel through time in your tests." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1977,6 +2057,7 @@ python-dateutil = "*" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1988,6 +2069,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1997,76 +2079,98 @@ files = [ [[package]] name = "tree-sitter" -version = "0.20.2" +version = "0.20.4" description = "Python bindings for the Tree-Sitter parsing library" +category = "main" optional = true python-versions = ">=3.3" files = [ - {file = "tree_sitter-0.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a151ccf9233b0b84850422654247f68a4d78f548425c76520402ea6fb6cdb24"}, - {file = "tree_sitter-0.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52ca2738c3c4c660c83054ac3e44a49cbecb9f89dc26bb8e154d6ca288aa06b0"}, - {file = "tree_sitter-0.20.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d51478ea078da7cc6f626e9e36f131bbc5fac036cf38ea4b5b81632cbac37d"}, - {file = "tree_sitter-0.20.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0b2b59e1633efbf19cd2ed1ceb8d51b2c44a278153b1113998c70bc1570b750"}, - {file = "tree_sitter-0.20.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7f691c57d2a65d6e53e2f3574153c9cd0c157ff938b8d6f252edd5e619811403"}, - {file = "tree_sitter-0.20.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba72a363387eebaff9a0b788f864fe47da425136cbd4cac6cd125051f043c296"}, - {file = "tree_sitter-0.20.2-cp310-cp310-win32.whl", hash = "sha256:55e33eb206446d5046d3b5fe36ab300840f5a8a844246adb0ccc68c55c30b722"}, - {file = "tree_sitter-0.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ce9d14daba0a71a778417d9d61dd4038ca96981ddec19e1e8990881469321c"}, - {file = "tree_sitter-0.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:942dbfb8bc380f09b0e323d3884de07d19022930516f33b7503a6eb5f6e18979"}, - {file = "tree_sitter-0.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee5651c11924d426f8d6858a40fd5090ae31574f81ef180bef2055282f43bf62"}, - {file = "tree_sitter-0.20.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fb6982b480031628dad7f229c4c8d90b17d4c281ba97848d3b100666d7fa45f"}, - {file = "tree_sitter-0.20.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:067609c6c7cb6e5a6c4be50076a380fe52b6e8f0641ee9d0da33b24a5b972e82"}, - {file = "tree_sitter-0.20.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:849d7e6b66fe7ded08a633943b30e0ed807eee76104288e6c6841433f4a9651b"}, - {file = "tree_sitter-0.20.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e85689573797e49f86e2d7cf48b9dd23bc044c477df074a78546e666d6990a29"}, - {file = "tree_sitter-0.20.2-cp311-cp311-win32.whl", hash = "sha256:098906148e44ea391a91b019d584dd8d0ea1437af62a9744e280e93163fd35ca"}, - {file = "tree_sitter-0.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:2753a87094b72fe7f02276b3948155618f53aa14e1ca20588f0eeed510f68512"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5de192cb9e7b1c882d45418decb7899f1547f7056df756bcae186bbf4966d96e"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3a77e663293a73a97edbf2a2e05001de08933eb5d311a16bdc25b9b2fac54f3"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415da4a70c56a003758537517fe9e60b8b0c5f70becde54cc8b8f3ba810adc70"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:707fb4d7a6123b8f9f2b005d61194077c3168c0372556e7418802280eddd4892"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:75fcbfb0a61ad64e7f787eb3f8fbf29b8e2b858dc011897ad039d838a06cee02"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-win32.whl", hash = "sha256:622926530895d939fa6e1e2487e71a311c71d3b09f4c4f19301695ea866304a4"}, - {file = "tree_sitter-0.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5c0712f031271d9bc462f1db7623d23703ed9fbcbaa6dc19ba535f58d6110774"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dfdf680ecf5619447243c4c20e4040a7b5e7afca4e1569f03c814e86bfda248"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79650ee23a15559b69542c71ed9eb3297dce21932a7c5c148be384dd0f2cd49d"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63059746b4b2f2f87dd19c208141c69452694aae32459b7a4ebca8539d13bf4"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9398d1e214d4915032cf68a678de7eb803f64d25ef04724d70b88db7bb7746e9"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b506fb2e2bd7a5a1603c644bbb90401fe488f86bbca39706addaa8d2bfc80815"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-win32.whl", hash = "sha256:405e83804ba60ca1c3dbd258adbe0d7b0f1bdce948e5eec5587a2ebedcf930ba"}, - {file = "tree_sitter-0.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a1e66d211c04144484e223922ac094a2367476e6f57000f986c5560dc5a83c6e"}, - {file = "tree_sitter-0.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f8adc325c74c042204ed47d095e0ec86f83de3c7ec4979645f86b58514f60297"}, - {file = "tree_sitter-0.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb49c861e1d111e0df119ecbfaa409e6413b8d91e8f56bcdb15f07fbc35594e"}, - {file = "tree_sitter-0.20.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e17ee83409b01fdd09021997b0c747be2f773bb2bb140ba6fb48b7e12fdd039a"}, - {file = "tree_sitter-0.20.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475ab841647a0d1bc1266c8978279f8e4f7b9520b9a7336d532e5dfc8910214d"}, - {file = "tree_sitter-0.20.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:222350189675d9814966a5c88c6c1378a2ee2f3041c439a6f1d1ff2006f403aa"}, - {file = "tree_sitter-0.20.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:31ea52f0deee70f2cb00aff01e40aae325a34ebe1661de274c9107322fb95f54"}, - {file = "tree_sitter-0.20.2-cp38-cp38-win32.whl", hash = "sha256:cceaf7287137cbca707006624a4a8d4b5ccbfec025793fde84d90524c2bb0946"}, - {file = "tree_sitter-0.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:25b9669911f21ec2b3727bb2f4dfeff6ddb6f81898c3e968d378a660e0d7f90e"}, - {file = "tree_sitter-0.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce30a17f46a6b39a04a599dea88c127a19e3e1f43a2ad0ced71b5c032d585077"}, - {file = "tree_sitter-0.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9576e8b2e663639527e01ab251b87f0bd370bfdd40515588689ebc424aec786"}, - {file = "tree_sitter-0.20.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d03731a498f624ce3536c821ef23b03d1ad569b3845b326a5b7149ef189d732c"}, - {file = "tree_sitter-0.20.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0116ecb163573ebaa0fc04cc99c90bd94c0be5cc4d0a1ebeb102de9cc9a054"}, - {file = "tree_sitter-0.20.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0943b00d3700f253c3ee6a53a71b9a6ca46defd9c0a33edb07a9388e70dc3a9e"}, - {file = "tree_sitter-0.20.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cb566b6f0b5457148cb8310a1ca3d764edf28e47fcccfe0b167861ecaa50c12"}, - {file = "tree_sitter-0.20.2-cp39-cp39-win32.whl", hash = "sha256:4544204a24c2b4d25d1731b0df83f7c819ce87c4f2538a19724b8753815ef388"}, - {file = "tree_sitter-0.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:9517b204e471d6aa59ee2232f6220f315ed5336079034d5c861a24660d6511d6"}, - {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:84343678f58cb354d22ed14b627056ffb33c540cf16c35a83db4eeee8827b935"}, - {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:611a80171d8fa6833dd0c8b022714d2ea789de15a955ec42ec4fd5fcc1032edb"}, - {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bacecfb61694c95ccee462742b3fcea50ba1baf115c42e60adf52b549ef642ce"}, - {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f344ae94a268479456f19712736cc7398de5822dc74cca7d39538c28085721d0"}, - {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:221784d7f326fe81ce7174ac5972800f58b9a7c5c48a03719cad9830c22e5a76"}, - {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64210ed8d2a1b7e2951f6576aa0cb7be31ad06d87da26c52961318fc54c7fe77"}, - {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2634ac73b39ceacfa431d6d95692eae7465977fa0b9e9f7ae6cb445991e829a5"}, - {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71663a0e8230dae99d9c55e6895bd2c9e42534ec861b255775f704ae2db70c1d"}, - {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32c3e0f30b45a58d36bf6a0ec982ca3eaa23c7f924628da499b7ad22a8abad71"}, - {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b02e4ab2158c25f6f520c93318d562da58fa4ba53e1dbd434be008f48104980"}, - {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10e567eb6961a1e86aebbe26a9ca07d324f8529bca90937a924f8aa0ea4dc127"}, - {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:63f8e8e69f5f25c2b565449e1b8a2aa7b6338b4f37c8658c5fbdec04858c30be"}, - {file = "tree_sitter-0.20.2.tar.gz", hash = "sha256:0a6c06abaa55de174241a476b536173bba28241d2ea85d198d33aa8bf009f028"}, + {file = "tree_sitter-0.20.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c259b9bcb596e54f54713eb3951226fc834d65289940f4bfdcdf519f08e8e876"}, + {file = "tree_sitter-0.20.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88da7e2e4c69881cd63916cc24ae0b809f96aae331da45b418ae6b2d1ed2ca19"}, + {file = "tree_sitter-0.20.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66a68b156ba131e9d8dff4a1f72037f4b368cc50c58f18905a91743ae1d1c795"}, + {file = "tree_sitter-0.20.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae28e25d551f406807011487bdfb9728041e656b30b554fa7f3391ab64ed69f9"}, + {file = "tree_sitter-0.20.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36b10c9c69e825ba65cf9b0f77668bf33e70d2a5764b64ad6f133f8cc9220f09"}, + {file = "tree_sitter-0.20.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7c18c64ddd44b75b7e1660b9793753eda427e4b145b6216d4b2d2e9b200c74f2"}, + {file = "tree_sitter-0.20.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9e9e594bbefb76ad9ea256f5c87eba7591b4758854d3df83ce4df415933a006"}, + {file = "tree_sitter-0.20.4-cp310-cp310-win32.whl", hash = "sha256:b4755229dc18644fe48bcab974bde09b171fcb6ef625d3cb5ece5c6198f4223e"}, + {file = "tree_sitter-0.20.4-cp310-cp310-win_amd64.whl", hash = "sha256:f792684cee8a46d9194d9f4223810e54ccc704470c5777538d59fbde0a4c91bf"}, + {file = "tree_sitter-0.20.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d22ee75f45836554ee6a11e50dd8f9827941e67c49fce9a0790245b899811a9"}, + {file = "tree_sitter-0.20.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a0ffd76dd991ba745bb5d0ba1d583bec85726d3ddef8c9685dc8636a619adde"}, + {file = "tree_sitter-0.20.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:060d4e5b803be0975f1ac46e54a292eab0701296ccd912f6cdac3f7331e29143"}, + {file = "tree_sitter-0.20.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822e02366dbf223697b2b56b8f91aa5b60571f9fe7c998988a381db1c69604e9"}, + {file = "tree_sitter-0.20.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:527ca72c6a8f60fa719af37fa86f58b7ad0e07b8f74d1c1c7e926c5c888a7e6b"}, + {file = "tree_sitter-0.20.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a418ca71309ea7052e076f08d623f33f58eae01a8e8cdc1e6d3a01b5b8ddebfe"}, + {file = "tree_sitter-0.20.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c3ba2561b61a83c28ca06a0bce2a5ffcfb6b39f9d27a45e5ebd9cad2bedb7f"}, + {file = "tree_sitter-0.20.4-cp311-cp311-win32.whl", hash = "sha256:8d04c75a389b2de94952d602264852acff8cd3ed1ccf8a2492a080973d5ddd58"}, + {file = "tree_sitter-0.20.4-cp311-cp311-win_amd64.whl", hash = "sha256:ba9215c0e7529d9eb370528e5d99b7389d14a7eae94f07d14fa9dab18f267c62"}, + {file = "tree_sitter-0.20.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c4c1af5ed4306071d30970c83ec882520a7bf5d8053996dbc4aa5c59238d4990"}, + {file = "tree_sitter-0.20.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9d70bfa550cf22c9cea9b3c0d18b889fc4f2a7e9dcf1d6cc93f49fa9d4a94954"}, + {file = "tree_sitter-0.20.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6de537bca0641775d8d175d37303d54998980fc0d997dd9aa89e16b415bf0cc3"}, + {file = "tree_sitter-0.20.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1c0f8c0e3e50267566f5116cdceedf4e23e8c08b55ef3becbe954a11b16e84"}, + {file = "tree_sitter-0.20.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef2ee6d9bb8e21713949e5ff769ed670fe1217f95b7eeb6c675788438c1e6e"}, + {file = "tree_sitter-0.20.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b6fd1c881ab0de5faa67168db2d001eee32be5482cb4e0b21b217689a05b6fe4"}, + {file = "tree_sitter-0.20.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf47047420021d50aec529cb66387c90562350b499ddf56ecef1fc8255439e30"}, + {file = "tree_sitter-0.20.4-cp312-cp312-win32.whl", hash = "sha256:c16b48378041fc9702b6aa3480f2ffa49ca8ea58141a862acd569e5a0679655f"}, + {file = "tree_sitter-0.20.4-cp312-cp312-win_amd64.whl", hash = "sha256:973e871167079a1b1d7304d361449253efbe2a6974728ad563cf407bd02ddccb"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9d33a55598dd18a4d8b869a3417de82a4812c3a7dc7e61cb025ece3e9c3e4e96"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cee6955c2c97fc5927a41c7a8b06647c4b4d9b99b8a1581bf1183435c8cec3e"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5022bea67e479ad212be7c05b983a72e297a013efb4e8ea5b5b4d7da79a9fdef"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:640f60a5b966f0990338f1bf559455c3dcb822bc4329d82b3d42f32a48374dfe"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0e83f641fe6f27d91bd4d259fff5d35de1567d3f581b9efe9bbd5be50fe4ddc7"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-win32.whl", hash = "sha256:ce6a85027c66fa3f09d482cc6d41927ea40955f7f33b86aedd26dd932709a2c9"}, + {file = "tree_sitter-0.20.4-cp36-cp36m-win_amd64.whl", hash = "sha256:fe10779347a6c067af29cb37fd4b75fa96c5cb68f587cc9530b70fe3f2a51a55"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28d5f84e34e276887e3a240b60906ca7e2b51e975f3145c3149ceed977a69508"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c913b65cbe10996116988ac436748f24883b5097e58274223e89bb2c5d1bb1a"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecaed46241e071752195a628bb97d2b740f2fde9e34f8a74456a4ea8bb26df88"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b641e88a97eab002a1736d93ef5a4beac90ea4fd6e25affd1831319b99f456c9"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:327c40f439c6155e4eee54c4657e4701a04f5f4816d9defdcb836bf65bf83d21"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-win32.whl", hash = "sha256:1b7c1d95f006b3de42fbf4045bd00c273d113e372fcb6a5378e74ed120c12032"}, + {file = "tree_sitter-0.20.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6140d037239a41046f5d34fba5e0374ee697adb4b48b90579c618b5402781c11"}, + {file = "tree_sitter-0.20.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f42fd1104efaad8151370f1936e2a488b7337a5d24544a9ab59ba4c4010b1272"}, + {file = "tree_sitter-0.20.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7859717c5d62ee386b3d036cab8ed0f88f8c027b6b4ae476a55a8c5fb8aab713"}, + {file = "tree_sitter-0.20.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdd361fe1cc68db68b4d85165641275e34b86cc26b2bab932790204fa14824dc"}, + {file = "tree_sitter-0.20.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b8d7539075606027b67764543463ff2bc4e52f4158ef6dc419c9f5625aa5383"}, + {file = "tree_sitter-0.20.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78e76307f05aca6cde72f3307b4d53701f34ae45f2248ceb83d1626051e201fd"}, + {file = "tree_sitter-0.20.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd8c352f4577f61098d06cf3feb7fd214259f41b5036b81003860ed54d16b448"}, + {file = "tree_sitter-0.20.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:281f3e5382d1bd7fccc88d1afe68c915565bc24f8b8dd4844079d46c7815b8a7"}, + {file = "tree_sitter-0.20.4-cp38-cp38-win32.whl", hash = "sha256:6a77ac3cdcddd80cdd1fd394318bff99f94f37e08d235aaefccb87e1224946e5"}, + {file = "tree_sitter-0.20.4-cp38-cp38-win_amd64.whl", hash = "sha256:8eee8adf54033dc48eab84b040f4d7b32355a964c4ae0aae5dfbdc4dbc3364ca"}, + {file = "tree_sitter-0.20.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e89f6508e30fce05e2c724725d022db30d877817b9d64f933506ffb3a3f4a2c2"}, + {file = "tree_sitter-0.20.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fb6286bb1fae663c45ff0700ec88fb9b50a81eed2bae8a291f95fcf8cc19547"}, + {file = "tree_sitter-0.20.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11e93f8b4bbae04070416a82257a7ab2eb0afb76e093ae3ea73bd63b792f6846"}, + {file = "tree_sitter-0.20.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8250725c5f78929aeb2c71db5dca76f1ef448389ca16f9439161f90978bb8478"}, + {file = "tree_sitter-0.20.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d404a8ca9de9b0843844f0cd4d423f46bc46375ab8afb63b1d8ec01201457ac8"}, + {file = "tree_sitter-0.20.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0f2422c9ee70ba972dfc3943746e6cf7fc03725a866908950245bda9ccfc7301"}, + {file = "tree_sitter-0.20.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21a937942e4729abbe778a609d2c218574436cb351c36fba89ef3c8c6066ec78"}, + {file = "tree_sitter-0.20.4-cp39-cp39-win32.whl", hash = "sha256:427a9a39360cc1816e28f8182550e478e4ba983595a2565ab9dfe32ea1b03fd7"}, + {file = "tree_sitter-0.20.4-cp39-cp39-win_amd64.whl", hash = "sha256:7095bb9aff297fa9c6026bf8914fd295997d714d1a6ee9a1edf7282c772f9f64"}, + {file = "tree_sitter-0.20.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:859260b90f0e3867ae840e39f54e830f607b3bc531bc21deeeeaa8a30cbb89ad"}, + {file = "tree_sitter-0.20.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dfc14be73cf46126660a3aecdd0396e69562ad1a902245225ca7bd29649594e"}, + {file = "tree_sitter-0.20.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec46355bf3ff23f54d5e365871ffd3e05cfbc65d1b36a8be7c0bcbda30a1d43"}, + {file = "tree_sitter-0.20.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d933a942fde39876b99c36f12aa3764e4a555ae9366c10ce6cca8c16341c1bbf"}, + {file = "tree_sitter-0.20.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7eec3b55135fe851a38fa248c9fd75fc3d58ceb6e1865b795e416e4d598c2a1"}, + {file = "tree_sitter-0.20.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc76225529ee14a53e84413480ce81ec3c44eaa0455c140e961c90ac3118ead"}, + {file = "tree_sitter-0.20.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf0396e47efffc0b528959a8f2e2346a98297579f867e9e1834c2aad4be829c"}, + {file = "tree_sitter-0.20.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a15fbabd3bc8e29c48289c156d743e69f5ec72bb125cf44f7adbdaa1937c3da6"}, + {file = "tree_sitter-0.20.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:36f8adf2126f496cf376b6e4b707cba061c25beb17841727eef6f0e083e53e1f"}, + {file = "tree_sitter-0.20.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841efb40c116ab0a066924925409a8a4dcffeb39a151c0b2a1c2abe56ad4fb42"}, + {file = "tree_sitter-0.20.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2051e8a70fd8426f27a43dad71d11929a62ce30a9b1eb65bba0ed79e82481592"}, + {file = "tree_sitter-0.20.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99a3c2824d4cfcffd9f961176891426bde2cb36ece5280c61480be93319c23c4"}, + {file = "tree_sitter-0.20.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:72830dc85a10430eca3d56739b7efcd7a05459c8d425f08c1aee6179ab7f13a9"}, + {file = "tree_sitter-0.20.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4992dd226055b6cd0a4f5661c66b799a73d3eff716302e0f7ab06594ee12d49f"}, + {file = "tree_sitter-0.20.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d95bbf92175cdc295d6d77f330942811f02e3aaf3fc64431cb749683b2f7d"}, + {file = "tree_sitter-0.20.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a25b1087e4f7825b2458dacf5f4b0be2938f78e850e822edca1ff4994b56081a"}, + {file = "tree_sitter-0.20.4.tar.gz", hash = "sha256:6adb123e2f3e56399bbf2359924633c882cc40ee8344885200bca0922f713be5"}, ] +[package.dependencies] +setuptools = {version = ">=60.0.0", markers = "python_version >= \"3.12\""} + [[package]] name = "tree-sitter-languages" version = "1.8.0" description = "Binary Python wheels for all tree sitter languages." +category = "main" optional = true python-versions = "*" files = [ @@ -2137,6 +2241,7 @@ tree-sitter = "*" name = "typed-ast" version = "1.5.5" description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2187,6 +2292,7 @@ files = [ name = "types-setuptools" version = "67.8.0.0" description = "Typing stubs for setuptools" +category = "dev" optional = false python-versions = "*" files = [ @@ -2196,19 +2302,21 @@ files = [ [[package]] name = "types-tree-sitter" -version = "0.20.1.5" +version = "0.20.1.6" description = "Typing stubs for tree-sitter" +category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "types-tree-sitter-0.20.1.5.tar.gz", hash = "sha256:94f971599548b90b9bbb6af651d235ad795a094a07651bc565a4b8856caebab1"}, - {file = "types_tree_sitter-0.20.1.5-py3-none-any.whl", hash = "sha256:8d7f9961febbad29789ce5c65f79b95b0702f3d34a7c12fabcd69c36c2bbe184"}, + {file = "types-tree-sitter-0.20.1.6.tar.gz", hash = "sha256:310a97916adf73553fd1bda8107884da9b638550ddc76085ae0875c8f520520c"}, + {file = "types_tree_sitter-0.20.1.6-py3-none-any.whl", hash = "sha256:40eae13bc44f4e36d4e97b52db674fe808c6ccb3036a7aed9a736313411fd057"}, ] [[package]] name = "types-tree-sitter-languages" version = "1.8.0.0" description = "Typing stubs for tree-sitter-languages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2223,6 +2331,7 @@ types-tree-sitter = "*" name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2234,6 +2343,7 @@ files = [ name = "tzdata" version = "2022.7" description = "Provider of IANA time zone data" +category = "dev" optional = false python-versions = ">=2" files = [ @@ -2245,6 +2355,7 @@ files = [ name = "uc-micro-py" version = "1.0.2" description = "Micro subset of unicode data files for linkify-it-py projects." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2259,6 +2370,7 @@ test = ["coverage", "pytest", "pytest-cov"] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2276,6 +2388,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "virtualenv" version = "20.24.6" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2297,6 +2410,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "watchdog" version = "3.0.0" description = "Filesystem events monitoring" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2336,6 +2450,7 @@ watchmedo = ["PyYAML (>=3.10)"] name = "yarl" version = "1.9.2" description = "Yet another URL library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2424,6 +2539,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.7" files = [ diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py index 783a829e98..aff345140f 100644 --- a/src/textual/document/_document.py +++ b/src/textual/document/_document.py @@ -195,6 +195,9 @@ def __init__(self, text: str) -> None: if text.endswith(tuple(VALID_NEWLINES)) or not text: self._lines.append("") + self.edit_count = 0 + """The number of edit operations performed on the document.""" + @property def lines(self) -> list[str]: """Get the document as a list of strings, where each string represents a line. @@ -233,6 +236,8 @@ def get_size(self, tab_width: int) -> Size: def replace_range(self, start: Location, end: Location, text: str) -> EditResult: """Replace text at the given range. + This is the only method by which a document may be updated. + Args: start: A tuple (row, column) where the edit starts. end: A tuple (row, column) where the edit ends. @@ -242,6 +247,8 @@ def replace_range(self, start: Location, end: Location, text: str) -> EditResult The EditResult containing information about the completed replace operation. """ + self.edit_count += 1 + top, bottom = sorted((start, end)) top_row, top_column = top bottom_row, bottom_column = bottom diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py new file mode 100644 index 0000000000..fe555306c4 --- /dev/null +++ b/src/textual/document/_wrapped_document.py @@ -0,0 +1,129 @@ +"""A view into a Document which wraps the document at a certain +width and can be queried to retrieve lines from the *wrapped* version +of the document.""" +from __future__ import annotations + +from rich.cells import chop_cells + +from textual.document._document import Document, Location + + +class WrappedDocumentView: + def __init__( + self, + document: Document, + width: int = 0, + ) -> None: + """Construct a WrappedDocumentView. + + Args: + document: The document to wrap. + width: The cell-width to wrap at. + """ + self._document = document + """The document wrapping is performed on.""" + + self._width = width + """The maximum cell-width per line.""" + + self._wrapped_lines: list[list[str]] = [] + """Cached wrapped document lines.""" + + self._wrap_all() + + self._last_edit_count = document.edit_count + """The edit_count of the document last time we wrapped. + + If the edit_count has not changed since we last performed + wrapping, we can rely on any cached data since we know the + document must be identical. + """ + + def _wrap_all(self) -> None: + """Wrap and cache all lines in the document.""" + new_wrapped_lines = [] + append_wrapped_line = new_wrapped_lines.append + width = self._width + + for line in self._document.lines: + append_wrapped_line(chop_cells(line, width)) + + self._wrapped_lines = new_wrapped_lines + + def refresh_range( + self, + start: Location, + old_end: Location, + new_end: Location, + ) -> None: + """Incrementally recompute wrapping based on a performed edit. + + Args: + start: The start location of the edit that was performed in document-space. + old_end: The old end location of the edit in document-space. + new_end: The new end location of the edit in document-space. + """ + + +''' +### Approach +- Introduce a secondary coordinate system "wrapped coordinates". This is in addition to the existing "document coordinates". +- Moving the cursor in any way should be done in wrapped-coordinate-space. +- When the cursor moves in wrapped coordinate space, the document coordinate space selection should be updated accordingly. + - So we need a mechanism to convert between wrapped coordinates and document coordinates. + +Lets add wrapping and *then* extract the `CodeEditor` functionality. + +- [x] Add `soft_wrap` boolean flag to TextArea. +- [ ] Display wrapped text +- [ ] Record cursor location in wrapped space +- [ ] Render the cursor based on the wrapped space instead of the document location +- [ ] When moving cursor, update location in wrapped space +- [ ] When the wrapped space location updates, update the document space location via watcher + +--- +### When are wrapped lines computed? +- When document is loaded, compute wrapped lines. +- When document is edited, re-compute and update all wrapped lines below the edited line. +- There are no other line wrapping computations performed. +### How do wrapped lines affect rendering? +- `render_line` will be indexing into the wrapped document, and displaying a view into that. This differs from the current approach, where it's a view + + +### When and what to wrap: + +- on document load: wrap whole document +- on edit: wrap affected line(s) - remember when we perform an edit, we are told the old start and new end locations, so we can find all the relevant lines which need to be re-wrapped. perform the re-wrapping of these lines, and insert the new lines into the corresponding position in the wrapped documents. TO SUPPORT THIS, our document view needs the ability to be informed about the lines impacted by an edit, and incrementally refresh the affected lines. + +can we associate lines in the document with their wrapped counterparts? +```python +wrapped_lines: list[list[str]] +"""A mapping of line indices to the wrapped versions of those lines.""" +``` +we fill this cache when the document is loaded, only if wrapping is enabled. + +when an edit occurs: +- delete the content on the lines between old_start and old_end +- take the new content and wrap it, ensuring we remember which document line each wrapped line corresponds to (use `list[list[str]]`) + - we may need a function like `apply_edit(from_line, to_line)` which internally grabs the relevant lines from the source document and wraps them +- insert the newly wrapped content into wrapped_line_cache at old_start (ensuring it pushes down content below it, rather than replacing) +- ensure a refresh is triggered + +**convert from document space to wrapped space** +(we need this to determine what should happen when we move the cursor, and where to render the cursor on screen) +- iterate over wrapped lines, summing up the lengths of all the wrapped lines to get to the document line. +- when we arrive at the line, take as many lines as possible from the value, then check how far into the final line we can go to arrive at the corresponding wrapped space location. +Note that we can also go from a document space line number and retrieve + +**convert from wrapped space to document space** +(to determine where in the document an insert should happen, since we insert into a position in document space) + +--- +there should be an efficient means of converting more than one location which prevents us from having to iterate through several lines for each location e.g. +`to_wrapped_space(*values: Location) -> tuple[Location]` +and similarly for the reverse direction! + +--- + + +''' diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index b77cc81ec7..75067faf08 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -757,6 +757,8 @@ def render_line(self, widget_y: int) -> Strip: Returns: A rendered line. """ + width = self.size.width + document = self.document scroll_x, scroll_y = self.scroll_offset From 8cef051c51401e94b8f35840e3d02954b10734de Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 12:27:52 +0000 Subject: [PATCH 004/150] Initial wrapping --- poetry.lock | 2391 +++++++++++---------- src/textual/document/_document.py | 10 + src/textual/document/_wrapped_document.py | 21 +- src/textual/widgets/_text_area.py | 8 + tests/document/test_wrapped_document.py | 0 5 files changed, 1221 insertions(+), 1209 deletions(-) create mode 100644 tests/document/test_wrapped_document.py diff --git a/poetry.lock b/poetry.lock index 3c33f0fde5..d1e6211fb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,3 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. - [[package]] name = "aiohttp" version = "3.8.6" @@ -7,95 +5,6 @@ description = "Async http client/server framework (asyncio)" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, -] [package.dependencies] aiosignal = ">=1.1.2" @@ -118,10 +27,6 @@ description = "aiosignal: a list of registered asynchronous callbacks" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] [package.dependencies] frozenlist = ">=1.1.0" @@ -133,10 +38,6 @@ description = "High level compatibility layer for multiple asynchronous event lo category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, -] [package.dependencies] exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} @@ -156,10 +57,6 @@ description = "Timeout context manager for asyncio programs" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] [package.dependencies] typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} @@ -171,10 +68,6 @@ description = "Enhance the standard unittest package with features for testing a category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] [[package]] name = "attrs" @@ -183,10 +76,6 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} @@ -205,10 +94,6 @@ description = "Internationalization utilities" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, -] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} @@ -224,33 +109,6 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, -] [package.dependencies] click = ">=8.0.0" @@ -275,22 +133,14 @@ description = "A decorator for caching properties in classes." category = "dev" optional = false python-versions = "*" -files = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, -] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] [[package]] name = "cfgv" @@ -299,10 +149,6 @@ description = "Validate configuration and produce human readable error messages. category = "dev" optional = false python-versions = ">=3.6.1" -files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] [[package]] name = "charset-normalizer" @@ -311,98 +157,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "dev" optional = false python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] [[package]] name = "click" @@ -411,10 +165,6 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -427,10 +177,6 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] [[package]] name = "colored" @@ -439,9 +185,6 @@ description = "Simple library for color and formatting to terminal" category = "dev" optional = false python-versions = "*" -files = [ - {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, -] [[package]] name = "coverage" @@ -450,68 +193,6 @@ description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] [package.extras] toml = ["tomli"] @@ -523,10 +204,6 @@ description = "Distribution utilities" category = "dev" optional = false python-versions = "*" -files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, -] [[package]] name = "exceptiongroup" @@ -535,10 +212,6 @@ description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, -] [package.extras] test = ["pytest (>=6)"] @@ -550,10 +223,6 @@ description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, -] [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] @@ -566,82 +235,6 @@ description = "A list-like structure which implements collections.abc.MutableSeq category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, - {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, - {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, - {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, - {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, - {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, - {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, - {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, - {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, - {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, - {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, -] [[package]] name = "ghp-import" @@ -650,10 +243,6 @@ description = "Copy your docs directly to the gh-pages branch." category = "dev" optional = false python-versions = "*" -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] [package.dependencies] python-dateutil = ">=2.8.1" @@ -668,10 +257,6 @@ description = "Git Object Database" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, -] [package.dependencies] smmap = ">=3.0.1,<6" @@ -683,10 +268,6 @@ description = "GitPython is a Python library used to interact with Git repositor category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, -] [package.dependencies] gitdb = ">=4.0.1,<5" @@ -702,10 +283,6 @@ description = "Signatures for entire Python programs. Extract the structure, the category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c"}, - {file = "griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109"}, -] [package.dependencies] cached-property = {version = "*", markers = "python_version < \"3.8\""} @@ -718,10 +295,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -733,10 +306,6 @@ description = "A minimal low-level HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, -] [package.dependencies] anyio = ">=3.0,<5.0" @@ -755,10 +324,6 @@ description = "The next generation HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, -] [package.dependencies] certifi = "*" @@ -779,10 +344,6 @@ description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, -] [package.extras] license = ["ukkonen"] @@ -794,10 +355,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] [[package]] name = "importlib-metadata" @@ -806,10 +363,6 @@ description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -827,10 +380,6 @@ description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] [[package]] name = "jinja2" @@ -839,10 +388,6 @@ description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] [package.dependencies] MarkupSafe = ">=2.0" @@ -857,10 +402,6 @@ description = "Links recognition library with FULL unicode support." category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, - {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, -] [package.dependencies] uc-micro-py = "*" @@ -878,10 +419,6 @@ description = "Python implementation of John Gruber's Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, - {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, -] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} @@ -897,10 +434,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, -] [package.dependencies] linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""} @@ -925,58 +458,6 @@ description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] [[package]] name = "mdit-py-plugins" @@ -985,10 +466,6 @@ description = "Collection of plugins for markdown-it-py" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, - {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, -] [package.dependencies] markdown-it-py = ">=1.0.0,<3.0.0" @@ -1005,10 +482,6 @@ description = "Markdown URL utilities" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] [[package]] name = "mergedeep" @@ -1017,10 +490,6 @@ description = "A deep merge function for 🐍." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] [[package]] name = "mkdocs" @@ -1029,10 +498,6 @@ description = "Project documentation with Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, - {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, -] [package.dependencies] click = ">=7.0" @@ -1062,10 +527,6 @@ description = "Automatically link across pages in MkDocs." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, -] [package.dependencies] Markdown = ">=3.3" @@ -1078,9 +539,6 @@ description = "A mkdocs plugin that lets you exclude files or trees." category = "dev" optional = false python-versions = "*" -files = [ - {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, -] [package.dependencies] mkdocs = "*" @@ -1092,10 +550,6 @@ description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs_material-9.2.7-py3-none-any.whl", hash = "sha256:92e4160d191cc76121fed14ab9f14638e43a6da0f2e9d7a9194d377f0a4e7f18"}, - {file = "mkdocs_material-9.2.7.tar.gz", hash = "sha256:b44da35b0d98cd762d09ef74f1ddce5b6d6e35c13f13beb0c9d82a629e5f229e"}, -] [package.dependencies] babel = ">=2.10,<3.0" @@ -1117,10 +571,6 @@ description = "Extension pack for Python Markdown and MkDocs Material." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs_material_extensions-1.2-py3-none-any.whl", hash = "sha256:c767bd6d6305f6420a50f0b541b0c9966d52068839af97029be14443849fb8a1"}, - {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"}, -] [[package]] name = "mkdocs-rss-plugin" @@ -1129,10 +579,6 @@ description = "MkDocs plugin which generates a static RSS feed using git log and category = "dev" optional = false python-versions = ">=3.7, <4" -files = [ - {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, - {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, -] [package.dependencies] GitPython = ">=3.1,<3.2" @@ -1151,10 +597,6 @@ description = "Automatic documentation from sources, for MkDocs." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, - {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, -] [package.dependencies] Jinja2 = ">=2.11.1" @@ -1177,10 +619,6 @@ description = "A Python handler for mkdocstrings." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocstrings_python-0.10.1-py3-none-any.whl", hash = "sha256:ef239cee2c688e2b949a0a47e42a141d744dd12b7007311b3309dc70e3bafc5c"}, - {file = "mkdocstrings_python-0.10.1.tar.gz", hash = "sha256:b72301fff739070ec517b5b36bf2f7c49d1360a275896a64efb97fc17d3f3968"}, -] [package.dependencies] griffe = ">=0.24" @@ -1193,71 +631,6 @@ description = "MessagePack serializer" category = "dev" optional = false python-versions = "*" -files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, -] [[package]] name = "multidict" @@ -1266,82 +639,6 @@ description = "multidict implementation" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] [[package]] name = "mypy" @@ -1350,34 +647,6 @@ description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, -] [package.dependencies] mypy-extensions = ">=1.0.0" @@ -1398,10 +667,6 @@ description = "Type system extensions for programs checked with the mypy type ch category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] [[package]] name = "nodeenv" @@ -1410,10 +675,6 @@ description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] [package.dependencies] setuptools = "*" @@ -1425,10 +686,6 @@ description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] [[package]] name = "paginate" @@ -1437,9 +694,6 @@ description = "Divides large result sets into pages for easier browsing" category = "dev" optional = false python-versions = "*" -files = [ - {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, -] [[package]] name = "pathspec" @@ -1448,10 +702,6 @@ description = "Utility library for gitignore style pattern matching of file path category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] [[package]] name = "platformdirs" @@ -1460,10 +710,6 @@ description = "A small Python package for determining appropriate platform-speci category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, -] [package.dependencies] typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} @@ -1479,10 +725,6 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, -] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -1498,10 +740,6 @@ description = "A framework for managing and maintaining multi-language pre-commi category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, -] [package.dependencies] cfgv = ">=2.0.0" @@ -1513,18 +751,15 @@ virtualenv = ">=20.10.0" [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.1" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, -] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" @@ -1533,10 +768,6 @@ description = "Extension pack for Python Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pymdown_extensions-10.2.1-py3-none-any.whl", hash = "sha256:bded105eb8d93f88f2f821f00108cb70cef1269db6a40128c09c5f48bfc60ea4"}, - {file = "pymdown_extensions-10.2.1.tar.gz", hash = "sha256:d0c534b4a5725a4be7ccef25d65a4c97dba58b54ad7c813babf0eb5ba9c81591"}, -] [package.dependencies] markdown = ">=3.2" @@ -1552,10 +783,6 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} @@ -1576,10 +803,6 @@ description = "Pytest plugin for aiohttp support" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, - {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, -] [package.dependencies] aiohttp = ">=3.8.1" @@ -1596,10 +819,6 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, - {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, -] [package.dependencies] pytest = ">=7.0.0" @@ -1616,10 +835,6 @@ description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, -] [package.dependencies] coverage = ">=5.2.1" @@ -1636,10 +851,6 @@ description = "Snapshot testing for Textual apps" category = "dev" optional = false python-versions = ">=3.6,<4.0" -files = [ - {file = "pytest_textual_snapshot-0.4.0-py3-none-any.whl", hash = "sha256:879cc5de29cdd31cfe1b6daeb1dc5e42682abebcf4f88e7e3375bd5200683fc0"}, - {file = "pytest_textual_snapshot-0.4.0.tar.gz", hash = "sha256:63782e053928a925d88ff7359dd640f2900e23bc708b3007f8b388e65f2527cb"}, -] [package.dependencies] jinja2 = ">=3.0.0" @@ -1655,10 +866,6 @@ description = "Extensions to the standard Python datetime module" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] [package.dependencies] six = ">=1.5" @@ -1670,10 +877,6 @@ description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" -files = [ - {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, - {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, -] [[package]] name = "pyyaml" @@ -1682,48 +885,6 @@ description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] [[package]] name = "pyyaml-env-tag" @@ -1732,22 +893,1161 @@ description = "A custom YAML tag for referencing environment variables in YAML f category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "regex" +version = "2022.10.31" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +category = "dev" +optional = false +python-versions = ">=3.7,<4" + +[package.dependencies] +colored = ">=1.3.92,<2.0.0" +pytest = ">=5.1.0,<8.0.0" + +[[package]] +name = "textual-dev" +version = "1.2.1" +description = "Development tools for working with Textual" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +aiohttp = ">=3.8.1" +click = ">=8.1.2" +msgpack = ">=1.0.3" +textual = ">=0.33.0" +typing-extensions = ">=4.4.0,<5.0.0" + +[[package]] +name = "time-machine" +version = "2.10.0" +description = "Travel through time in your tests." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +python-dateutil = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tree-sitter" +version = "0.20.4" +description = "Python bindings for the Tree-Sitter parsing library" +category = "main" +optional = true +python-versions = ">=3.3" + +[package.dependencies] +setuptools = {version = ">=60.0.0", markers = "python_version >= \"3.12\""} + +[[package]] +name = "tree-sitter-languages" +version = "1.8.0" +description = "Binary Python wheels for all tree sitter languages." +category = "main" +optional = true +python-versions = "*" + +[package.dependencies] +tree-sitter = "*" + +[[package]] +name = "typed-ast" +version = "1.5.5" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "types-setuptools" +version = "67.8.0.0" +description = "Typing stubs for setuptools" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-tree-sitter" +version = "0.20.1.6" +description = "Typing stubs for tree-sitter" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "types-tree-sitter-languages" +version = "1.8.0.0" +description = "Typing stubs for tree-sitter-languages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +types-tree-sitter = "*" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.7" +description = "Provider of IANA time zone data" +category = "dev" +optional = false +python-versions = ">=2" + +[[package]] +name = "uc-micro-py" +version = "1.0.2" +description = "Micro subset of unicode data files for linkify-it-py projects." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["coverage", "pytest", "pytest-cov"] + +[[package]] +name = "urllib3" +version = "2.0.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.24.6" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +syntax = ["tree-sitter", "tree_sitter_languages"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "fdb89c91b37a0d781f9cbfb7bd8cffd737e369e21761d67113c1a91882f28158" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, + {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, + {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, + {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, + {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, + {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, + {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, + {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, + {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, + {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, + {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, +] +aiosignal = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] +anyio = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] +async-timeout = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] +asynctest = [ + {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, + {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, +] +attrs = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] +babel = [ + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, +] +black = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] +cached-property = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] +certifi = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] +click = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +colored = [ + {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, +] +coverage = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] +distlib = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] +filelock = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] +frozenlist = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] +ghp-import = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] +gitdb = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] +gitpython = [ + {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, + {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, +] +griffe = [ + {file = "griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c"}, + {file = "griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109"}, +] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +httpcore = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] +httpx = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] +identify = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +importlib-metadata = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] +iniconfig = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] +jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +linkify-it-py = [ + {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, + {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, +] +markdown = [ + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, +] +markdown-it-py = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] +mdit-py-plugins = [ + {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, + {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, +] +mdurl = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +mergedeep = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] +mkdocs = [ + {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, + {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, +] +mkdocs-autorefs = [ + {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, + {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, +] +mkdocs-exclude = [ + {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, +] +mkdocs-material = [ + {file = "mkdocs_material-9.2.7-py3-none-any.whl", hash = "sha256:92e4160d191cc76121fed14ab9f14638e43a6da0f2e9d7a9194d377f0a4e7f18"}, + {file = "mkdocs_material-9.2.7.tar.gz", hash = "sha256:b44da35b0d98cd762d09ef74f1ddce5b6d6e35c13f13beb0c9d82a629e5f229e"}, +] +mkdocs-material-extensions = [ + {file = "mkdocs_material_extensions-1.2-py3-none-any.whl", hash = "sha256:c767bd6d6305f6420a50f0b541b0c9966d52068839af97029be14443849fb8a1"}, + {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"}, +] +mkdocs-rss-plugin = [ + {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, + {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, +] +mkdocstrings = [ + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, +] +mkdocstrings-python = [ + {file = "mkdocstrings_python-0.10.1-py3-none-any.whl", hash = "sha256:ef239cee2c688e2b949a0a47e42a141d744dd12b7007311b3309dc70e3bafc5c"}, + {file = "mkdocstrings_python-0.10.1.tar.gz", hash = "sha256:b72301fff739070ec517b5b36bf2f7c49d1360a275896a64efb97fc17d3f3968"}, +] +msgpack = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] +multidict = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] +mypy = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] +mypy-extensions = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] +nodeenv = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] +packaging = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] +paginate = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] +pathspec = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] +platformdirs = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] +pluggy = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +pre-commit = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] +pygments = [ + {file = "pygments-2.17.1-py3-none-any.whl", hash = "sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4"}, + {file = "pygments-2.17.1.tar.gz", hash = "sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8"}, +] +pymdown-extensions = [ + {file = "pymdown_extensions-10.2.1-py3-none-any.whl", hash = "sha256:bded105eb8d93f88f2f821f00108cb70cef1269db6a40128c09c5f48bfc60ea4"}, + {file = "pymdown_extensions-10.2.1.tar.gz", hash = "sha256:d0c534b4a5725a4be7ccef25d65a4c97dba58b54ad7c813babf0eb5ba9c81591"}, +] +pytest = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] +pytest-aiohttp = [ + {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, + {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, +] +pytest-cov = [ + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] +pytest-textual-snapshot = [ + {file = "pytest_textual_snapshot-0.4.0-py3-none-any.whl", hash = "sha256:879cc5de29cdd31cfe1b6daeb1dc5e42682abebcf4f88e7e3375bd5200683fc0"}, + {file = "pytest_textual_snapshot-0.4.0.tar.gz", hash = "sha256:63782e053928a925d88ff7359dd640f2900e23bc708b3007f8b388e65f2527cb"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, +] +pyyaml = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] +pyyaml-env-tag = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "regex" -version = "2022.10.31" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +regex = [ {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, @@ -1832,168 +2132,48 @@ files = [ {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"}, {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"}, {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"}, - {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, - {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, - {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, - {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rich" -version = "13.7.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, + {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, + {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, + {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, +] +requests = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +rich = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] +setuptools = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] - -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +smmap = [ {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, ] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] - -[[package]] -name = "syrupy" -version = "3.0.6" -description = "Pytest Snapshot Test Utility" -category = "dev" -optional = false -python-versions = ">=3.7,<4" -files = [ +syrupy = [ {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, ] - -[package.dependencies] -colored = ">=1.3.92,<2.0.0" -pytest = ">=5.1.0,<8.0.0" - -[[package]] -name = "textual-dev" -version = "1.2.1" -description = "Development tools for working with Textual" -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" -files = [ +textual-dev = [ {file = "textual_dev-1.2.1-py3-none-any.whl", hash = "sha256:a96ff43841cadf853dd689d68c2fc920a23ad71cfa9a33917ca53e96d1cc81f3"}, {file = "textual_dev-1.2.1.tar.gz", hash = "sha256:0bda11adfc541e0cc9e49bdf37a8b852281dc2387bb6ff3d01f40c7a3f841684"}, ] - -[package.dependencies] -aiohttp = ">=3.8.1" -click = ">=8.1.2" -msgpack = ">=1.0.3" -textual = ">=0.33.0" -typing-extensions = ">=4.4.0,<5.0.0" - -[[package]] -name = "time-machine" -version = "2.10.0" -description = "Travel through time in your tests." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +time-machine = [ {file = "time_machine-2.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d5e93c14b935d802a310c1d4694a9fe894b48a733ebd641c9a570d6f9e1f667"}, {file = "time_machine-2.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c0dda6b132c0180941944ede357109016d161d840384c2fb1096a3a2ef619f4"}, {file = "time_machine-2.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:900517e4a4121bf88527343d6aea2b5c99df134815bb8271ef589ec792502a71"}, @@ -2049,42 +2229,15 @@ files = [ {file = "time_machine-2.10.0-cp39-cp39-win_arm64.whl", hash = "sha256:c1775a949dd830579d1af5a271ec53d920dc01657035ad305f55c5a1ac9b9f1e"}, {file = "time_machine-2.10.0.tar.gz", hash = "sha256:64fd89678cf589fc5554c311417128b2782222dd65f703bf248ef41541761da0"}, ] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] - -[[package]] -name = "tree-sitter" -version = "0.20.4" -description = "Python bindings for the Tree-Sitter parsing library" -category = "main" -optional = true -python-versions = ">=3.3" -files = [ +tree-sitter = [ {file = "tree_sitter-0.20.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c259b9bcb596e54f54713eb3951226fc834d65289940f4bfdcdf519f08e8e876"}, {file = "tree_sitter-0.20.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88da7e2e4c69881cd63916cc24ae0b809f96aae331da45b418ae6b2d1ed2ca19"}, {file = "tree_sitter-0.20.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66a68b156ba131e9d8dff4a1f72037f4b368cc50c58f18905a91743ae1d1c795"}, @@ -2162,18 +2315,7 @@ files = [ {file = "tree_sitter-0.20.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a25b1087e4f7825b2458dacf5f4b0be2938f78e850e822edca1ff4994b56081a"}, {file = "tree_sitter-0.20.4.tar.gz", hash = "sha256:6adb123e2f3e56399bbf2359924633c882cc40ee8344885200bca0922f713be5"}, ] - -[package.dependencies] -setuptools = {version = ">=60.0.0", markers = "python_version >= \"3.12\""} - -[[package]] -name = "tree-sitter-languages" -version = "1.8.0" -description = "Binary Python wheels for all tree sitter languages." -category = "main" -optional = true -python-versions = "*" -files = [ +tree-sitter-languages = [ {file = "tree_sitter_languages-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a20045f0c7a8394ac0c085c3a7da88438f9e62c6a8b661ebf63c3edb8c3f2bf6"}, {file = "tree_sitter_languages-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ef80d5896b420d434f7322abbc2c5a5548a37b3821c5486ed0612d2bd760d5a"}, {file = "tree_sitter_languages-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e7c7100c7b4a364035417e811ab8d43c8ee4e38d0c6ab9cad9c4d8133c0abd"}, @@ -2233,18 +2375,7 @@ files = [ {file = "tree_sitter_languages-1.8.0-cp39-cp39-win32.whl", hash = "sha256:3e9eafc7079114783b5385a769fd190c93525bcae3cf6791fd819c617067394e"}, {file = "tree_sitter_languages-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:9d30b7f48f18a60eea9a0f9494e0f0ea6f560d861770a84c3faab8d7a446fc55"}, ] - -[package.dependencies] -tree-sitter = "*" - -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +typed-ast = [ {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, @@ -2287,133 +2418,39 @@ files = [ {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] - -[[package]] -name = "types-setuptools" -version = "67.8.0.0" -description = "Typing stubs for setuptools" -category = "dev" -optional = false -python-versions = "*" -files = [ +types-setuptools = [ {file = "types-setuptools-67.8.0.0.tar.gz", hash = "sha256:95c9ed61871d6c0e258433373a4e1753c0a7c3627a46f4d4058c7b5a08ab844f"}, {file = "types_setuptools-67.8.0.0-py3-none-any.whl", hash = "sha256:6df73340d96b238a4188b7b7668814b37e8018168aef1eef94a3b1872e3f60ff"}, ] - -[[package]] -name = "types-tree-sitter" -version = "0.20.1.6" -description = "Typing stubs for tree-sitter" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +types-tree-sitter = [ {file = "types-tree-sitter-0.20.1.6.tar.gz", hash = "sha256:310a97916adf73553fd1bda8107884da9b638550ddc76085ae0875c8f520520c"}, {file = "types_tree_sitter-0.20.1.6-py3-none-any.whl", hash = "sha256:40eae13bc44f4e36d4e97b52db674fe808c6ccb3036a7aed9a736313411fd057"}, ] - -[[package]] -name = "types-tree-sitter-languages" -version = "1.8.0.0" -description = "Typing stubs for tree-sitter-languages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +types-tree-sitter-languages = [ {file = "types-tree-sitter-languages-1.8.0.0.tar.gz", hash = "sha256:a066d1c91d5fe8b8fce08669816d9e8c41bbe348085b3cb9799fa74070a30604"}, {file = "types_tree_sitter_languages-1.8.0.0-py3-none-any.whl", hash = "sha256:9d4a8e2a435a4a0d356e643fb53993e3c491749ce0b7a628c22cb87904c6daca"}, ] - -[package.dependencies] -types-tree-sitter = "*" - -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +typing-extensions = [ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] - -[[package]] -name = "tzdata" -version = "2022.7" -description = "Provider of IANA time zone data" -category = "dev" -optional = false -python-versions = ">=2" -files = [ +tzdata = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, {file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"}, ] - -[[package]] -name = "uc-micro-py" -version = "1.0.2" -description = "Micro subset of unicode data files for linkify-it-py projects." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +uc-micro-py = [ {file = "uc-micro-py-1.0.2.tar.gz", hash = "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54"}, {file = "uc_micro_py-1.0.2-py3-none-any.whl", hash = "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0"}, ] - -[package.extras] -test = ["coverage", "pytest", "pytest-cov"] - -[[package]] -name = "urllib3" -version = "2.0.7" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +urllib3 = [ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.24.6" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +virtualenv = [ {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, ] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} -platformdirs = ">=3.9.1,<4" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +watchdog = [ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, @@ -2442,18 +2479,7 @@ files = [ {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, ] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "yarl" -version = "1.9.2" -description = "Yet another URL library" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +yarl = [ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, @@ -2529,32 +2555,7 @@ files = [ {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, ] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +zipp = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[extras] -syntax = ["tree-sitter", "tree_sitter_languages"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.7" -content-hash = "fdb89c91b37a0d781f9cbfb7bd8cffd737e369e21761d67113c1a91882f28158" diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py index aff345140f..da603d7439 100644 --- a/src/textual/document/_document.py +++ b/src/textual/document/_document.py @@ -91,6 +91,16 @@ def text(self) -> str: def newline(self) -> Newline: """Return the line separator used in the document.""" + @property + @abstractmethod + def lines(self) -> list[str]: + """Get the lines of the document as a list of strings. + + The strings should *not* include newline characters. The newline + character used for the document can be retrieved via the newline + property. + """ + @abstractmethod def get_line(self, index: int) -> str: """Returns the line with the given index from the document. diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index fe555306c4..702dce2c70 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -5,13 +5,13 @@ from rich.cells import chop_cells -from textual.document._document import Document, Location +from textual.document._document import DocumentBase, Location class WrappedDocumentView: def __init__( self, - document: Document, + document: DocumentBase, width: int = 0, ) -> None: """Construct a WrappedDocumentView. @@ -29,17 +29,7 @@ def __init__( self._wrapped_lines: list[list[str]] = [] """Cached wrapped document lines.""" - self._wrap_all() - - self._last_edit_count = document.edit_count - """The edit_count of the document last time we wrapped. - - If the edit_count has not changed since we last performed - wrapping, we can rely on any cached data since we know the - document must be identical. - """ - - def _wrap_all(self) -> None: + def wrap_all(self) -> None: """Wrap and cache all lines in the document.""" new_wrapped_lines = [] append_wrapped_line = new_wrapped_lines.append @@ -64,6 +54,10 @@ def refresh_range( new_end: The new end location of the edit in document-space. """ + # Get the start and the end lines of the edit in document space + # Convert to wrapped space (remember that the line numbers in wrapped + # space correspond to the line numbers in coordinate space). + ''' ### Approach @@ -125,5 +119,4 @@ def refresh_range( --- - ''' diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 75067faf08..f00f254f32 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -27,6 +27,7 @@ SyntaxAwareDocument, SyntaxAwareDocumentError, ) +from textual.document._wrapped_document import WrappedDocumentView from textual.expand_tabs import expand_tabs_inline if TYPE_CHECKING: @@ -343,6 +344,9 @@ def __init__( self.soft_wrap = soft_wrap """Enable or disable soft wrapping.""" + self._wrapped_document: WrappedDocumentView = WrappedDocumentView() + """""" + @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. @@ -656,6 +660,10 @@ def _set_document(self, text: str, language: str | None) -> None: document = Document(text) self.document = document + + self._wrapped_document = WrappedDocumentView(document) + self._wrapped_document.wrap_all() + self._build_highlight_map() @property diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py new file mode 100644 index 0000000000..e69de29bb2 From 56419332bbb6560209dae68b76303d3375def043 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 12:59:45 +0000 Subject: [PATCH 005/150] Begin implementing wrapping --- src/textual/document/_wrapped_document.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 702dce2c70..9513c72605 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -54,7 +54,14 @@ def refresh_range( new_end: The new end location of the edit in document-space. """ - # Get the start and the end lines of the edit in document space + # Get all the text on the lines between start and end in document space + start_row, _ = start + end_row, _ = new_end + new_text = self._document.lines[start_row:] + + # Wrap the text on these lines + wrapped_new_text = chop_cells(new_text, self._width) + # Convert to wrapped space (remember that the line numbers in wrapped # space correspond to the line numbers in coordinate space). From add3abdde60731488e02a36bfa5ccaceafb544c3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 15:15:48 +0000 Subject: [PATCH 006/150] Add tests for wrapped document --- src/textual/document/_wrapped_document.py | 22 ++++++++++-- tests/document/test_wrapped_document.py | 42 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 702dce2c70..82ac11bbe1 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -1,6 +1,10 @@ """A view into a Document which wraps the document at a certain width and can be queried to retrieve lines from the *wrapped* version -of the document.""" +of the document. + +Allows for incremental updates, ensuring that we only re-wrap ranges of the document +that were influenced by edits. +""" from __future__ import annotations from rich.cells import chop_cells @@ -8,7 +12,7 @@ from textual.document._document import DocumentBase, Location -class WrappedDocumentView: +class WrappedDocument: def __init__( self, document: DocumentBase, @@ -40,7 +44,17 @@ def wrap_all(self) -> None: self._wrapped_lines = new_wrapped_lines - def refresh_range( + @property + def lines(self) -> list[list[str]]: + """The lines of the wrapped version of the Document. + + Each index in the returned list represents a line index in the raw + document. The list[str] at each index is the content of the raw document line + split into multiple lines via wrapping. + """ + return self._wrapped_lines + + def recompute_range( self, start: Location, old_end: Location, @@ -48,6 +62,8 @@ def refresh_range( ) -> None: """Incrementally recompute wrapping based on a performed edit. + This must be called *after* the source document has been edited. + Args: start: The start location of the edit that was performed in document-space. old_end: The old end location of the edit in document-space. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index e69de29bb2..63bf61fe21 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -0,0 +1,42 @@ +from textual.document._document import Document +from textual.document._wrapped_document import WrappedDocument + +SIMPLE_TEXT = "1234567\n12345\n123456789\n" + + +def test_wrapped_document_lines(): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + + assert wrapped_document.lines == [ + ["1234", "567"], + ["1234", "5"], + ["1234", "5678", "9"], + [""], + ] + + +def test_wrapped_document_refresh_range(): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + + # Before the document was edited, it wraps as normal. + assert wrapped_document.lines == [ + ["1234", "567"], + ["1234", "5"], + ["1234", "5678", "9"], + [""], + ] + + start_location = (1, 0) + old_end_location = (3, 0) + + edit_result = document.replace_range(start_location, old_end_location, "123") + + # Inform the wrapped document about the range impacted by the edit + wrapped_document.recompute_range( + start_location, old_end_location, edit_result.end_location + ) + + # Now confirm the resulting wrapped version is as we would expect + assert wrapped_document.lines == [["1234", "567"], ["1234"], [""]] From a3a2600607c7e2724f180e12822ea710a509c3b0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 15:23:18 +0000 Subject: [PATCH 007/150] Fixing some naming issues --- src/textual/widgets/_text_area.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f00f254f32..984e4f99a7 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -27,7 +27,7 @@ SyntaxAwareDocument, SyntaxAwareDocumentError, ) -from textual.document._wrapped_document import WrappedDocumentView +from textual.document._wrapped_document import WrappedDocument from textual.expand_tabs import expand_tabs_inline if TYPE_CHECKING: @@ -344,7 +344,7 @@ def __init__( self.soft_wrap = soft_wrap """Enable or disable soft wrapping.""" - self._wrapped_document: WrappedDocumentView = WrappedDocumentView() + self._wrapped_document = WrappedDocument() """""" @staticmethod @@ -661,7 +661,7 @@ def _set_document(self, text: str, language: str | None) -> None: self.document = document - self._wrapped_document = WrappedDocumentView(document) + self._wrapped_document = WrappedDocument(document) self._wrapped_document.wrap_all() self._build_highlight_map() From 95417a42f15fc3a83814af24318837a7ea786077 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 15:45:06 +0000 Subject: [PATCH 008/150] Fix refresh_range wrapping test --- src/textual/document/_wrapped_document.py | 18 +++++++++++++++--- tests/document/test_wrapped_document.py | 6 ++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 82ac11bbe1..3f370b8829 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -54,7 +54,7 @@ def lines(self) -> list[list[str]]: """ return self._wrapped_lines - def recompute_range( + def refresh_range( self, start: Location, old_end: Location, @@ -71,8 +71,20 @@ def recompute_range( """ # Get the start and the end lines of the edit in document space - # Convert to wrapped space (remember that the line numbers in wrapped - # space correspond to the line numbers in coordinate space). + start_row, _ = start + end_row, _ = new_end + + # +1 since we go to the start of the next row, and +1 for inclusive. + new_lines = self._document.lines[start_row : end_row + 2] + + wrapped_new_lines = [] + for line in new_lines: + wrapped_line = chop_cells(line, self._width) + wrapped_new_lines.append(wrapped_line) + + # Replace the range start->old with the new wrapped lines + old_end_row, _ = old_end + self._wrapped_lines[start_row:old_end_row] = wrapped_new_lines ''' diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 63bf61fe21..360acd965e 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -7,6 +7,7 @@ def test_wrapped_document_lines(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap_all() assert wrapped_document.lines == [ ["1234", "567"], @@ -19,6 +20,7 @@ def test_wrapped_document_lines(): def test_wrapped_document_refresh_range(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap_all() # Before the document was edited, it wraps as normal. assert wrapped_document.lines == [ @@ -34,9 +36,9 @@ def test_wrapped_document_refresh_range(): edit_result = document.replace_range(start_location, old_end_location, "123") # Inform the wrapped document about the range impacted by the edit - wrapped_document.recompute_range( + wrapped_document.refresh_range( start_location, old_end_location, edit_result.end_location ) # Now confirm the resulting wrapped version is as we would expect - assert wrapped_document.lines == [["1234", "567"], ["1234"], [""]] + assert wrapped_document.lines == [["1234", "567"], ["123"], [""]] From 2dda263e41353e7f5415417459b4d71545b9a8eb Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 15:55:12 +0000 Subject: [PATCH 009/150] Test wrapping in longer text --- tests/document/test_wrapped_document.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 360acd965e..c2e6969b15 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -18,6 +18,7 @@ def test_wrapped_document_lines(): def test_wrapped_document_refresh_range(): + """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap_all() @@ -42,3 +43,37 @@ def test_wrapped_document_refresh_range(): # Now confirm the resulting wrapped version is as we would expect assert wrapped_document.lines == [["1234", "567"], ["123"], [""]] + + +def test_wrapped_document_refresh_range_new_text_wrapped(): + """The post-edit content itself must be wrapped.""" + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap_all() + + # Before the document was edited, it wraps as normal. + assert wrapped_document.lines == [ + ["1234", "567"], + ["1234", "5"], + ["1234", "5678", "9"], + [""], + ] + + start_location = (1, 0) + old_end_location = (3, 0) + + edit_result = document.replace_range( + start_location, old_end_location, "12 34567 8901" + ) + + # Inform the wrapped document about the range impacted by the edit + wrapped_document.refresh_range( + start_location, old_end_location, edit_result.end_location + ) + + # Now confirm the resulting wrapped version is as we would expect + assert wrapped_document.lines == [ + ["1234", "567"], + ["12 3", "4567", " 890", "1"], + [""], + ] From 2c9f7644f83d21196600dc767f98db844125240a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 17:09:32 +0000 Subject: [PATCH 010/150] Maintaining wrapping offsets --- src/textual/document/_wrapped_document.py | 26 ++++++++++++++--------- tests/document/test_wrapped_document.py | 5 ++--- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 3f370b8829..0a17d39ede 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -7,7 +7,8 @@ """ from __future__ import annotations -from rich.cells import chop_cells +from rich._wrap import divide_line +from rich.text import Text from textual.document._document import DocumentBase, Location @@ -30,8 +31,9 @@ def __init__( self._width = width """The maximum cell-width per line.""" - self._wrapped_lines: list[list[str]] = [] - """Cached wrapped document lines.""" + self._wrap_offsets: list[list[int]] = [] + """Maps line indices to the offsets within the line wrapping + breaks should be added.""" def wrap_all(self) -> None: """Wrap and cache all lines in the document.""" @@ -40,9 +42,9 @@ def wrap_all(self) -> None: width = self._width for line in self._document.lines: - append_wrapped_line(chop_cells(line, width)) + append_wrapped_line(divide_line(line, width)) - self._wrapped_lines = new_wrapped_lines + self._wrap_offsets = new_wrapped_lines @property def lines(self) -> list[list[str]]: @@ -52,7 +54,11 @@ def lines(self) -> list[list[str]]: document. The list[str] at each index is the content of the raw document line split into multiple lines via wrapping. """ - return self._wrapped_lines + wrapped_lines = [] + for line_index, line in enumerate(self._document.lines): + divided = Text(line).divide(self._wrap_offsets[line_index]) + wrapped_lines.append([section.plain for section in divided]) + return wrapped_lines def refresh_range( self, @@ -77,14 +83,14 @@ def refresh_range( # +1 since we go to the start of the next row, and +1 for inclusive. new_lines = self._document.lines[start_row : end_row + 2] - wrapped_new_lines = [] + new_wrap_offsets = [] for line in new_lines: - wrapped_line = chop_cells(line, self._width) - wrapped_new_lines.append(wrapped_line) + wrapped_line = divide_line(line, self._width) + new_wrap_offsets.append(wrapped_line) # Replace the range start->old with the new wrapped lines old_end_row, _ = old_end - self._wrapped_lines[start_row:old_end_row] = wrapped_new_lines + self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets ''' diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index c2e6969b15..f8eb40ee1a 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -42,7 +42,7 @@ def test_wrapped_document_refresh_range(): ) # Now confirm the resulting wrapped version is as we would expect - assert wrapped_document.lines == [["1234", "567"], ["123"], [""]] + assert wrapped_document.lines == [["1234", "567"], ["123"]] def test_wrapped_document_refresh_range_new_text_wrapped(): @@ -74,6 +74,5 @@ def test_wrapped_document_refresh_range_new_text_wrapped(): # Now confirm the resulting wrapped version is as we would expect assert wrapped_document.lines == [ ["1234", "567"], - ["12 3", "4567", " 890", "1"], - [""], + ["12 ", "3456", "7 ", "8901"], ] From aab538fb0c3341b156df6d386a2612f818d9723a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 17:12:16 +0000 Subject: [PATCH 011/150] Remove irrelevant code --- src/textual/widgets/_text_area.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 984e4f99a7..971cb345cb 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -27,7 +27,6 @@ SyntaxAwareDocument, SyntaxAwareDocumentError, ) -from textual.document._wrapped_document import WrappedDocument from textual.expand_tabs import expand_tabs_inline if TYPE_CHECKING: @@ -213,9 +212,6 @@ class TextArea(ScrollView, can_focus=True): The text selected in the document is available via the `TextArea.selected_text` property. """ - soft_wrap: Reactive[bool] = reactive(False) - """Enable or disable soft wrapping.""" - show_line_numbers: Reactive[bool] = reactive(True) """True to show the line number column on the left edge, otherwise False. @@ -275,7 +271,6 @@ def __init__( *, language: str | None = None, theme: str | None = None, - soft_wrap: bool = False, name: str | None = None, id: str | None = None, classes: str | None = None, @@ -341,12 +336,6 @@ def __init__( self.theme = theme - self.soft_wrap = soft_wrap - """Enable or disable soft wrapping.""" - - self._wrapped_document = WrappedDocument() - """""" - @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. @@ -660,10 +649,6 @@ def _set_document(self, text: str, language: str | None) -> None: document = Document(text) self.document = document - - self._wrapped_document = WrappedDocument(document) - self._wrapped_document.wrap_all() - self._build_highlight_map() @property From 18fc68733ecd2f9d9ee77fb6f87908b6e00aff40 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 17:13:44 +0000 Subject: [PATCH 012/150] Remove more irrelevant code --- src/textual/widgets/_text_area.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 971cb345cb..67fbe43ce2 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -750,8 +750,6 @@ def render_line(self, widget_y: int) -> Strip: Returns: A rendered line. """ - width = self.size.width - document = self.document scroll_x, scroll_y = self.scroll_offset From 325ec78234bf40f41adac1e65aeefbc6a6d3513c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 20 Nov 2023 17:14:22 +0000 Subject: [PATCH 013/150] Remove a comment --- src/textual/document/_wrapped_document.py | 63 ----------------------- 1 file changed, 63 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 0a17d39ede..6ff44c016a 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -91,66 +91,3 @@ def refresh_range( # Replace the range start->old with the new wrapped lines old_end_row, _ = old_end self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets - - -''' -### Approach -- Introduce a secondary coordinate system "wrapped coordinates". This is in addition to the existing "document coordinates". -- Moving the cursor in any way should be done in wrapped-coordinate-space. -- When the cursor moves in wrapped coordinate space, the document coordinate space selection should be updated accordingly. - - So we need a mechanism to convert between wrapped coordinates and document coordinates. - -Lets add wrapping and *then* extract the `CodeEditor` functionality. - -- [x] Add `soft_wrap` boolean flag to TextArea. -- [ ] Display wrapped text -- [ ] Record cursor location in wrapped space -- [ ] Render the cursor based on the wrapped space instead of the document location -- [ ] When moving cursor, update location in wrapped space -- [ ] When the wrapped space location updates, update the document space location via watcher - ---- -### When are wrapped lines computed? -- When document is loaded, compute wrapped lines. -- When document is edited, re-compute and update all wrapped lines below the edited line. -- There are no other line wrapping computations performed. -### How do wrapped lines affect rendering? -- `render_line` will be indexing into the wrapped document, and displaying a view into that. This differs from the current approach, where it's a view - - -### When and what to wrap: - -- on document load: wrap whole document -- on edit: wrap affected line(s) - remember when we perform an edit, we are told the old start and new end locations, so we can find all the relevant lines which need to be re-wrapped. perform the re-wrapping of these lines, and insert the new lines into the corresponding position in the wrapped documents. TO SUPPORT THIS, our document view needs the ability to be informed about the lines impacted by an edit, and incrementally refresh the affected lines. - -can we associate lines in the document with their wrapped counterparts? -```python -wrapped_lines: list[list[str]] -"""A mapping of line indices to the wrapped versions of those lines.""" -``` -we fill this cache when the document is loaded, only if wrapping is enabled. - -when an edit occurs: -- delete the content on the lines between old_start and old_end -- take the new content and wrap it, ensuring we remember which document line each wrapped line corresponds to (use `list[list[str]]`) - - we may need a function like `apply_edit(from_line, to_line)` which internally grabs the relevant lines from the source document and wraps them -- insert the newly wrapped content into wrapped_line_cache at old_start (ensuring it pushes down content below it, rather than replacing) -- ensure a refresh is triggered - -**convert from document space to wrapped space** -(we need this to determine what should happen when we move the cursor, and where to render the cursor on screen) -- iterate over wrapped lines, summing up the lengths of all the wrapped lines to get to the document line. -- when we arrive at the line, take as many lines as possible from the value, then check how far into the final line we can go to arrive at the corresponding wrapped space location. -Note that we can also go from a document space line number and retrieve - -**convert from wrapped space to document space** -(to determine where in the document an insert should happen, since we insert into a position in document space) - ---- -there should be an efficient means of converting more than one location which prevents us from having to iterate through several lines for each location e.g. -`to_wrapped_space(*values: Location) -> tuple[Location]` -and similarly for the reverse direction! - ---- - -''' From 6d2c500e5b4ce17336c35c5c40566de771c67f17 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 14:14:12 +0000 Subject: [PATCH 014/150] Implementing WrappedDocument required utility methods --- src/textual/document/_wrapped_document.py | 52 ++++++++++++++++++++--- tests/document/test_wrapped_document.py | 18 +++++--- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 7b4ec19ed5..a85745d5ad 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -35,16 +35,20 @@ def __init__( """Maps line indices to the offsets within the line wrapping breaks should be added.""" + self._offset_to_document_line: list[int] = [] + """Allows us to quickly go from a y-offset within the wrapped document + to the index of the line in the raw document.""" + def wrap_all(self) -> None: """Wrap and cache all lines in the document.""" - new_wrapped_lines = [] - append_wrapped_line = new_wrapped_lines.append + new_wrap_offsets = [] + append_wrap_offset = new_wrap_offsets.append width = self._width for line in self._document.lines: - append_wrapped_line(divide_line(line, width)) + append_wrap_offset(divide_line(line, width)) - self._wrap_offsets = new_wrapped_lines + self._wrap_offsets = new_wrap_offsets @property def lines(self) -> list[list[str]]: @@ -84,10 +88,44 @@ def refresh_range( new_lines = self._document.lines[start_row : end_row + 2] new_wrap_offsets = [] - for line in new_lines: - wrapped_line = divide_line(line, self._width) - new_wrap_offsets.append(wrapped_line) + for line_index, line in enumerate(new_lines, start_row): + wrap_offsets = divide_line(line, self._width) + new_wrap_offsets.append(wrap_offsets) # Replace the range start->old with the new wrapped lines old_end_row, _ = old_end self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets + + def offset_to_line_index(self, offset: int) -> int: + """Given an offset within the wrapped/visual display of the document, + return the corresponding line index. + + Args: + offset: The y-offset within the document. + + Returns: + The line index corresponding to the given y-offset. + """ + + # The offset will always be greater than or equal to the line index, + # since a wrapped line is always equal to or greater than a line in terms of + # height/y-offset. + + current_offset = 0 + for line_index, line_offsets in enumerate(self._wrap_offsets): + wrapped_line_height = len(line_offsets) + 1 + current_offset += wrapped_line_height + if current_offset >= offset: + return line_index + + def get_offsets(self, line_index: int) -> list[int]: + """Given a line index, get the offsets within that line where wrapping + should occur for the current document. + + Args: + line_index: The index of the line within the document. + + Returns: + The offsets within the line where wrapping should occur. + """ + return self._wrap_offsets[line_index] diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index f8eb40ee1a..f0b671bb16 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -4,7 +4,7 @@ SIMPLE_TEXT = "1234567\n12345\n123456789\n" -def test_wrapped_document_lines(): +def test_wrap_all(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap_all() @@ -17,7 +17,7 @@ def test_wrapped_document_lines(): ] -def test_wrapped_document_refresh_range(): +def test_refresh_range(): """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) @@ -45,7 +45,7 @@ def test_wrapped_document_refresh_range(): assert wrapped_document.lines == [["1234", "567"], ["123"]] -def test_wrapped_document_refresh_range_new_text_wrapped(): +def test_refresh_range_new_text_wrapped(): """The post-edit content itself must be wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) @@ -54,9 +54,9 @@ def test_wrapped_document_refresh_range_new_text_wrapped(): # Before the document was edited, it wraps as normal. assert wrapped_document.lines == [ ["1234", "567"], - ["1234", "5"], + ["1234", "5"], # selection starts at start of this line ["1234", "5678", "9"], - [""], + [""], # selection ends here, and includes this line ] start_location = (1, 0) @@ -76,3 +76,11 @@ def test_wrapped_document_refresh_range_new_text_wrapped(): ["1234", "567"], ["12 ", "3456", "7 ", "8901"], ] + + +def test_offset_to_line_index_empty_document(): + document = Document("") + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap_all() + + assert wrapped_document.lines == [[""]] From a770b390ebd9091bc1bf61ba7023668099275d57 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 14:26:35 +0000 Subject: [PATCH 015/150] Testing offset to line_index --- src/textual/document/_wrapped_document.py | 4 ++-- tests/document/test_wrapped_document.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index a85745d5ad..993d0ddd27 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -19,7 +19,7 @@ def __init__( document: DocumentBase, width: int = 0, ) -> None: - """Construct a WrappedDocumentView. + """Construct a WrappedDocument. Args: document: The document to wrap. @@ -115,7 +115,7 @@ def offset_to_line_index(self, offset: int) -> int: for line_index, line_offsets in enumerate(self._wrap_offsets): wrapped_line_height = len(line_offsets) + 1 current_offset += wrapped_line_height - if current_offset >= offset: + if current_offset > offset: return line_index def get_offsets(self, line_index: int) -> list[int]: diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index f0b671bb16..2d2dd11675 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -1,3 +1,5 @@ +import pytest + from textual.document._document import Document from textual.document._wrapped_document import WrappedDocument @@ -84,3 +86,24 @@ def test_offset_to_line_index_empty_document(): wrapped_document.wrap_all() assert wrapped_document.lines == [[""]] + + +@pytest.mark.parametrize( + "offset,line_index", + [ + (0, 0), + (1, 0), + (2, 1), + (3, 1), + (4, 2), + (5, 2), + (6, 2), + (7, 3), + ], +) +def test_offset_to_line_index(offset, line_index): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap_all() + + assert wrapped_document.offset_to_line_index(offset) == line_index From 63ad2c2762909f9f8322daed1df74453fbd93f75 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 14:38:23 +0000 Subject: [PATCH 016/150] Test WrappedDocument offset error cases --- src/textual/document/_wrapped_document.py | 19 +++++++++++++++---- tests/document/test_wrapped_document.py | 20 +++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 993d0ddd27..fa66573e2c 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -39,7 +39,7 @@ def __init__( """Allows us to quickly go from a y-offset within the wrapped document to the index of the line in the raw document.""" - def wrap_all(self) -> None: + def wrap(self) -> None: """Wrap and cache all lines in the document.""" new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append @@ -103,13 +103,22 @@ def offset_to_line_index(self, offset: int) -> int: Args: offset: The y-offset within the document. + Raises: + ValueError: When the given offset does not correspond to a line + in the document. + Returns: The line index corresponding to the given y-offset. """ - # The offset will always be greater than or equal to the line index, - # since a wrapped line is always equal to or greater than a line in terms of - # height/y-offset. + def invalid_offset_error(): + raise ValueError( + f"No line exists at wrapped document offset {offset!r}. " + f"Document wrapped with width {self._width!r}. " + ) + + if offset < 0: + invalid_offset_error() current_offset = 0 for line_index, line_offsets in enumerate(self._wrap_offsets): @@ -118,6 +127,8 @@ def offset_to_line_index(self, offset: int) -> int: if current_offset > offset: return line_index + invalid_offset_error() # Offset is greater than wrapped document height. + def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping should occur for the current document. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 2d2dd11675..63dc9021ba 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -9,7 +9,7 @@ def test_wrap_all(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap_all() + wrapped_document.wrap() assert wrapped_document.lines == [ ["1234", "567"], @@ -23,7 +23,7 @@ def test_refresh_range(): """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap_all() + wrapped_document.wrap() # Before the document was edited, it wraps as normal. assert wrapped_document.lines == [ @@ -51,7 +51,7 @@ def test_refresh_range_new_text_wrapped(): """The post-edit content itself must be wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap_all() + wrapped_document.wrap() # Before the document was edited, it wraps as normal. assert wrapped_document.lines == [ @@ -83,7 +83,7 @@ def test_refresh_range_new_text_wrapped(): def test_offset_to_line_index_empty_document(): document = Document("") wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap_all() + wrapped_document.wrap() assert wrapped_document.lines == [[""]] @@ -104,6 +104,16 @@ def test_offset_to_line_index_empty_document(): def test_offset_to_line_index(offset, line_index): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap_all() + wrapped_document.wrap() assert wrapped_document.offset_to_line_index(offset) == line_index + + +@pytest.mark.parametrize("offset", [-3, 1000]) +def test_offset_to_line_index_invalid_offset_raises_exception(offset): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + with pytest.raises(ValueError): + wrapped_document.offset_to_line_index(offset) From b3b3900e576c6e33584d9174b743c5409f05847c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 14:51:31 +0000 Subject: [PATCH 017/150] Testing getting offsets --- src/textual/document/_wrapped_document.py | 9 ++++- tests/document/test_wrapped_document.py | 40 +++++++++++------------ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index fa66573e2c..c9dcaa2ca6 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -139,4 +139,11 @@ def get_offsets(self, line_index: int) -> list[int]: Returns: The offsets within the line where wrapping should occur. """ - return self._wrap_offsets[line_index] + wrap_offsets = self._wrap_offsets + out_of_bounds = line_index < 0 or line_index >= len(wrap_offsets) + if out_of_bounds: + raise ValueError( + f"The document line index {line_index!r} is out of bounds. " + f"The document contains {len(wrap_offsets)!r} lines." + ) + return wrap_offsets[line_index] diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 63dc9021ba..a6881e3be2 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -3,7 +3,7 @@ from textual.document._document import Document from textual.document._wrapped_document import WrappedDocument -SIMPLE_TEXT = "1234567\n12345\n123456789\n" +SIMPLE_TEXT = "123 4567\n12345\n123456789\n" def test_wrap_all(): @@ -12,7 +12,7 @@ def test_wrap_all(): wrapped_document.wrap() assert wrapped_document.lines == [ - ["1234", "567"], + ["123 ", "4567"], ["1234", "5"], ["1234", "5678", "9"], [""], @@ -25,14 +25,6 @@ def test_refresh_range(): wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() - # Before the document was edited, it wraps as normal. - assert wrapped_document.lines == [ - ["1234", "567"], - ["1234", "5"], - ["1234", "5678", "9"], - [""], - ] - start_location = (1, 0) old_end_location = (3, 0) @@ -44,7 +36,7 @@ def test_refresh_range(): ) # Now confirm the resulting wrapped version is as we would expect - assert wrapped_document.lines == [["1234", "567"], ["123"]] + assert wrapped_document.lines == [["123 ", "4567"], ["123"]] def test_refresh_range_new_text_wrapped(): @@ -53,14 +45,6 @@ def test_refresh_range_new_text_wrapped(): wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() - # Before the document was edited, it wraps as normal. - assert wrapped_document.lines == [ - ["1234", "567"], - ["1234", "5"], # selection starts at start of this line - ["1234", "5678", "9"], - [""], # selection ends here, and includes this line - ] - start_location = (1, 0) old_end_location = (3, 0) @@ -75,7 +59,7 @@ def test_refresh_range_new_text_wrapped(): # Now confirm the resulting wrapped version is as we would expect assert wrapped_document.lines == [ - ["1234", "567"], + ["123 ", "4567"], ["12 ", "3456", "7 ", "8901"], ] @@ -117,3 +101,19 @@ def test_offset_to_line_index_invalid_offset_raises_exception(offset): with pytest.raises(ValueError): wrapped_document.offset_to_line_index(offset) + + +@pytest.mark.parametrize( + "line_index, offsets", + [ + (0, [4]), + (1, [4]), + (2, [4, 8]), + ], +) +def test_get_offsets(line_index, offsets): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + assert wrapped_document.get_offsets(line_index) == offsets From 8d6784b7cc76a9e34870f31be340dc4fe375cb04 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 15:10:56 +0000 Subject: [PATCH 018/150] Testing invalid line numbers being passed to WrappedDocument.get_offsets --- tests/document/test_wrapped_document.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index a6881e3be2..379c52694c 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -117,3 +117,13 @@ def test_get_offsets(line_index, offsets): wrapped_document.wrap() assert wrapped_document.get_offsets(line_index) == offsets + + +@pytest.mark.parametrize("line_index", [-4, 10000]) +def test_get_offsets_invalid_line_index(line_index): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + with pytest.raises(ValueError): + wrapped_document.get_offsets(line_index) From abd466766bfe2478e251dd3653cb384c392de2bc Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 15:29:44 +0000 Subject: [PATCH 019/150] Small optimisations --- src/textual/document/_wrapped_document.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index c9dcaa2ca6..20a1a0d4f4 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -25,7 +25,7 @@ def __init__( document: The document to wrap. width: The cell-width to wrap at. """ - self._document = document + self.document = document """The document wrapping is performed on.""" self._width = width @@ -45,7 +45,7 @@ def wrap(self) -> None: append_wrap_offset = new_wrap_offsets.append width = self._width - for line in self._document.lines: + for line in self.document.lines: append_wrap_offset(divide_line(line, width)) self._wrap_offsets = new_wrap_offsets @@ -59,9 +59,10 @@ def lines(self) -> list[list[str]]: split into multiple lines via wrapping. """ wrapped_lines = [] - for line_index, line in enumerate(self._document.lines): + append = wrapped_lines.append + for line_index, line in enumerate(self.document.lines): divided = Text(line).divide(self._wrap_offsets[line_index]) - wrapped_lines.append([section.plain for section in divided]) + append([section.plain for section in divided]) return wrapped_lines def refresh_range( @@ -85,12 +86,13 @@ def refresh_range( end_row, _ = new_end # +1 since we go to the start of the next row, and +1 for inclusive. - new_lines = self._document.lines[start_row : end_row + 2] + new_lines = self.document.lines[start_row : end_row + 2] new_wrap_offsets = [] + append_wrap_offset = new_wrap_offsets.append for line_index, line in enumerate(new_lines, start_row): wrap_offsets = divide_line(line, self._width) - new_wrap_offsets.append(wrap_offsets) + append_wrap_offset(wrap_offsets) # Replace the range start->old with the new wrapped lines old_end_row, _ = old_end From dbe056b6718a7fc6f9abae4427b04baf12dd341c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 15:35:22 +0000 Subject: [PATCH 020/150] Fix docstring --- src/textual/document/_wrapped_document.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 20a1a0d4f4..73b78f7d52 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -138,6 +138,9 @@ def get_offsets(self, line_index: int) -> list[int]: Args: line_index: The index of the line within the document. + Raises: + ValueError: When `line_index` is out of bounds. + Returns: The offsets within the line where wrapping should occur. """ From 6444ad0b0b02c17bdf1da133ec2411d1dfabb9ac Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 21 Nov 2023 20:39:03 +0000 Subject: [PATCH 021/150] Update src/textual/document/_wrapped_document.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --- src/textual/document/_wrapped_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 73b78f7d52..d4597d3e63 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -32,7 +32,7 @@ def __init__( """The maximum cell-width per line.""" self._wrap_offsets: list[list[int]] = [] - """Maps line indices to the offsets within the line wrapping + """Maps line indices to the offsets within the line where wrapping breaks should be added.""" self._offset_to_document_line: list[int] = [] From 988e7ab3861a46e475c7a7adf887931e58630cf0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 22 Nov 2023 12:31:35 +0000 Subject: [PATCH 022/150] Extract logic for converting cell widths to column indices --- src/textual/_cells.py | 23 ++++++++++++++++++++++- src/textual/widgets/_text_area.py | 10 ++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/textual/_cells.py b/src/textual/_cells.py index 2c23b0e3ea..db9cac5c67 100644 --- a/src/textual/_cells.py +++ b/src/textual/_cells.py @@ -1,6 +1,8 @@ from typing import Callable -__all__ = ["cell_len"] +from textual.expand_tabs import expand_tabs_inline + +__all__ = ["cell_len", "cell_width_to_column_index"] cell_len: Callable[[str], int] @@ -8,3 +10,22 @@ from rich.cells import cached_cell_len as cell_len except ImportError: from rich.cells import cell_len + + +def cell_width_to_column_index(line: str, cell_width: int, tab_width: int) -> int: + """Retrieve the column index corresponding to the given cell width. + + Args: + line: The line of text to search within. + cell_width: The cell width to convert to column index. + tab_width: The tab stop width to expand tabs contained within the line. + + Returns: + The column corresponding to the cell width. + """ + total_cell_offset = 0 + for column_index, character in enumerate(line): + total_cell_offset += cell_len(expand_tabs_inline(character, tab_width)) + if total_cell_offset >= cell_width + 1: + return column_index + return len(line) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 9880c78236..f534883253 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -33,7 +33,7 @@ from tree_sitter import Language from textual import events, log -from textual._cells import cell_len +from textual._cells import cell_len, cell_width_to_column_index from textual.binding import Binding from textual.events import Message, MouseEvent from textual.geometry import Offset, Region, Size, Spacing, clamp @@ -1118,14 +1118,8 @@ def cell_width_to_column_index(self, cell_width: int, row_index: int) -> int: Returns: The column corresponding to the cell width on that row. """ - tab_width = self.indent_width - total_cell_offset = 0 line = self.document[row_index] - for column_index, character in enumerate(line): - total_cell_offset += cell_len(expand_tabs_inline(character, tab_width)) - if total_cell_offset >= cell_width + 1: - return column_index - return len(line) + return cell_width_to_column_index(line, cell_width, self.indent_width) def clamp_visitable(self, location: Location) -> Location: """Clamp the given location to the nearest visitable location. From e2ff0c4c433c3648696316fc3f1e0b314e056b59 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 22 Nov 2023 15:41:44 +0000 Subject: [PATCH 023/150] Fix click target issue when tabs not fully expanded --- src/textual/_cells.py | 21 +++++++++++--- src/textual/expand_tabs.py | 56 ++++++++++++++++++++++++++------------ tests/test_expand_tabs.py | 25 +++++++++++++++++ 3 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 tests/test_expand_tabs.py diff --git a/src/textual/_cells.py b/src/textual/_cells.py index db9cac5c67..716faaed66 100644 --- a/src/textual/_cells.py +++ b/src/textual/_cells.py @@ -1,6 +1,6 @@ from typing import Callable -from textual.expand_tabs import expand_tabs_inline +from textual.expand_tabs import get_tab_widths __all__ = ["cell_len", "cell_width_to_column_index"] @@ -23,9 +23,22 @@ def cell_width_to_column_index(line: str, cell_width: int, tab_width: int) -> in Returns: The column corresponding to the cell width. """ + column_index = 0 total_cell_offset = 0 - for column_index, character in enumerate(line): - total_cell_offset += cell_len(expand_tabs_inline(character, tab_width)) - if total_cell_offset >= cell_width + 1: + for part, expanded_tab_width in get_tab_widths(line, tab_width): + # Check if the click landed on a character within this part. + for character in part: + total_cell_offset += cell_len(character) + if total_cell_offset > cell_width: + return column_index + column_index += 1 + + # Account for the appearance of the tab character for this part + total_cell_offset += expanded_tab_width + # Check if the click falls within the boundary of the expanded tab. + if total_cell_offset > cell_width: return column_index + + column_index += 1 + return len(line) diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 9227f796c2..71751dd2c1 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -7,37 +7,59 @@ _TABS_SPLITTER_RE = re.compile(r"(.*?\t|.+?$)") -def expand_tabs_inline(line: str, tab_size: int = 4) -> str: - """Expands tabs, taking into account double cell characters. +def get_tab_widths(line: str, tab_size: int = 4) -> list[tuple[str, int]]: + """Splits a string line into tuples (str, int). Args: line: The text to expand tabs in. tab_size: Number of cells in a tab. + Returns: - New string with tabs replaced with spaces. + Each tuple represents a section of the line which precedes a tab character. + The string is the string text that appears before the tab character (excluding the tab). + The integer is the width that the tab character is expanded to. """ - if "\t" not in line: - return line - new_line_parts: list[str] = [] - add_part = new_line_parts.append + + parts: list[tuple[str, int]] = [] + add_part = parts.append cell_position = 0 - parts = _TABS_SPLITTER_RE.findall(line) + matches = _TABS_SPLITTER_RE.findall(line) - for part in parts: - if part.endswith("\t"): - part = f"{part[:-1]} " - cell_position += cell_len(part) + for match in matches: + expansion_width = 0 + if match.endswith("\t"): + # Remove the tab, and check the width of the rest of the line. + match = match[:-1] + cell_position += cell_len(match) + + # Now move along the line by the width of the tab. tab_remainder = cell_position % tab_size - if tab_remainder: - spaces = tab_size - tab_remainder - part += spaces * " " - add_part(part) + expansion_width = tab_size - tab_remainder + cell_position += expansion_width + + add_part((match, expansion_width)) + + return parts + - return "".join(new_line_parts) +def expand_tabs_inline(line: str, tab_size: int = 4) -> str: + """Expands tabs, taking into account double cell characters. + + Args: + line: The text to expand tabs in. + tab_size: Number of cells in a tab. + Returns: + New string with tabs replaced with spaces. + """ + tab_widths = get_tab_widths(line, tab_size) + return "".join( + [part + expansion_width * " " for part, expansion_width in tab_widths] + ) if __name__ == "__main__": print(expand_tabs_inline("\tbar")) + print(expand_tabs_inline("\tbar\t")) print(expand_tabs_inline("1\tbar")) print(expand_tabs_inline("12\tbar")) print(expand_tabs_inline("123\tbar")) diff --git a/tests/test_expand_tabs.py b/tests/test_expand_tabs.py new file mode 100644 index 0000000000..d1d31fb110 --- /dev/null +++ b/tests/test_expand_tabs.py @@ -0,0 +1,25 @@ +import pytest + +from textual.expand_tabs import expand_tabs_inline + + +@pytest.mark.parametrize( + "line, expanded_line", + [ + (" b ar ", " b ar "), + ("\tbar", " bar"), + ("\tbar\t", " bar "), + ("\tr\t", " r "), + ("1\tbar", "1 bar"), + ("12\tbar", "12 bar"), + ("123\tbar", "123 bar"), + ("1234\tbar", "1234 bar"), + ("💩\tbar", "💩 bar"), + ("💩💩\tbar", "💩💩 bar"), + ("💩💩💩\tbar", "💩💩💩 bar"), + ("F💩\tbar", "F💩 bar"), + ("F💩O\tbar", "F💩O bar"), + ], +) +def test_expand_tabs_inline(line, expanded_line): + assert expand_tabs_inline(line) == expanded_line From 6c5a98a8fb9c71d61688c9a1f54c74fc84464968 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 22 Nov 2023 16:40:17 +0000 Subject: [PATCH 024/150] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af17082efd..648b45d44c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased + +- Fixed mouse targeting issue in `TextArea` when tabs were not fully expanded https://github.com/Textualize/textual/pull/3725 + ## [0.42.0] - 2023-11-22 ### Fixed From e196e013855497d5d5e804fec9b4d7931bea2519 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 22 Nov 2023 16:44:34 +0000 Subject: [PATCH 025/150] Change line_offset_to_index to offset_to_location (it now works in two dimensions) to support click targeting correctly when a document is wrapped. --- src/textual/document/_wrapped_document.py | 43 +++++++++++++++++++---- tests/document/test_wrapped_document.py | 43 ++++++++++++----------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 73b78f7d52..27e98787aa 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -10,7 +10,9 @@ from rich._wrap import divide_line from rich.text import Text +from textual._cells import cell_width_to_column_index from textual.document._document import DocumentBase, Location +from textual.geometry import Offset class WrappedDocument: @@ -98,12 +100,13 @@ def refresh_range( old_end_row, _ = old_end self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets - def offset_to_line_index(self, offset: int) -> int: + def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, return the corresponding line index. Args: offset: The y-offset within the document. + tab_width: The maximum width of tab characters in the document. Raises: ValueError: When the given offset does not correspond to a line @@ -115,19 +118,47 @@ def offset_to_line_index(self, offset: int) -> int: def invalid_offset_error(): raise ValueError( - f"No line exists at wrapped document offset {offset!r}. " - f"Document wrapped with width {self._width!r}. " + f"No location exists at wrapped document offset {offset!r}. " + f"Document is wrapped with width {self._width!r}." ) - if offset < 0: + x, y = offset + if y < 0: invalid_offset_error() current_offset = 0 + target_line_index = None + target_line_offsets = None for line_index, line_offsets in enumerate(self._wrap_offsets): wrapped_line_height = len(line_offsets) + 1 current_offset += wrapped_line_height - if current_offset > offset: - return line_index + if current_offset > y: + # We've found the vertical offset. + target_line_index = line_index + target_line_offsets = line_offsets + break + + if target_line_index is None: + invalid_offset_error() + + wrapped_line_index = y - current_offset - 1 # negative indexing + if wrapped_line_index < 0: + # We've found the relevant line, now find the character by + # looking at the character corresponding to the offset width. + wrapped_lines = Text(self.document[target_line_index]).divide( + target_line_offsets + ) + + # wrapped_section is the text that appears on a single y_offset within + # the TextArea. It's a potentially wrapped portion of a larger line from + # the original document. + wrapped_section = wrapped_lines[wrapped_line_index].plain + + # Get the column index within this wrapped section of the line + target_column_index = cell_width_to_column_index( + wrapped_section, x, tab_width + ) + return Location(target_line_index, target_column_index) invalid_offset_error() # Offset is greater than wrapped document height. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 379c52694c..aa7997bd0a 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -2,11 +2,12 @@ from textual.document._document import Document from textual.document._wrapped_document import WrappedDocument +from textual.geometry import Offset SIMPLE_TEXT = "123 4567\n12345\n123456789\n" -def test_wrap_all(): +def test_wrap(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() @@ -19,6 +20,14 @@ def test_wrap_all(): ] +def test_wrap_empty_document(): + document = Document("") + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + assert wrapped_document.lines == [[""]] + + def test_refresh_range(): """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) @@ -64,33 +73,25 @@ def test_refresh_range_new_text_wrapped(): ] -def test_offset_to_line_index_empty_document(): - document = Document("") - wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() - - assert wrapped_document.lines == [[""]] - - @pytest.mark.parametrize( - "offset,line_index", + "offset,location", [ - (0, 0), - (1, 0), - (2, 1), - (3, 1), - (4, 2), - (5, 2), - (6, 2), - (7, 3), + (Offset(0, 0), (0, 0)), + # (1, 0), + # (2, 1), + # (3, 1), + # (4, 2), + # (5, 2), + # (6, 2), + # (7, 3), ], ) -def test_offset_to_line_index(offset, line_index): +def test_offset_to_location(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() - assert wrapped_document.offset_to_line_index(offset) == line_index + assert wrapped_document.offset_to_location(offset, 2) == location @pytest.mark.parametrize("offset", [-3, 1000]) @@ -100,7 +101,7 @@ def test_offset_to_line_index_invalid_offset_raises_exception(offset): wrapped_document.wrap() with pytest.raises(ValueError): - wrapped_document.offset_to_line_index(offset) + wrapped_document.offset_to_location(offset) @pytest.mark.parametrize( From 5f57396579dc608e1637f555bf83210b36c5e5c9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 11:49:01 +0000 Subject: [PATCH 026/150] Fix wrapping offset calculation --- src/textual/document/_wrapped_document.py | 50 ++++++++++++----------- tests/document/test_wrapped_document.py | 12 +++--- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 7074baaf2e..ed026b40ba 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -130,37 +130,41 @@ def invalid_offset_error(): target_line_index = None target_line_offsets = None for line_index, line_offsets in enumerate(self._wrap_offsets): - wrapped_line_height = len(line_offsets) + 1 - current_offset += wrapped_line_height - if current_offset > y: + next_offset = current_offset + len(line_offsets) + 1 + if next_offset > y: # We've found the vertical offset. target_line_index = line_index target_line_offsets = line_offsets break + current_offset = next_offset if target_line_index is None: invalid_offset_error() - wrapped_line_index = y - current_offset - 1 # negative indexing - if wrapped_line_index < 0: - # We've found the relevant line, now find the character by - # looking at the character corresponding to the offset width. - wrapped_lines = Text(self.document[target_line_index]).divide( - target_line_offsets - ) - - # wrapped_section is the text that appears on a single y_offset within - # the TextArea. It's a potentially wrapped portion of a larger line from - # the original document. - wrapped_section = wrapped_lines[wrapped_line_index].plain - - # Get the column index within this wrapped section of the line - target_column_index = cell_width_to_column_index( - wrapped_section, x, tab_width - ) - return Location(target_line_index, target_column_index) - - invalid_offset_error() # Offset is greater than wrapped document height. + # We've found the relevant line, now find the character by + # looking at the character corresponding to the offset width. + wrapped_lines = Text(self.document[target_line_index]).divide( + target_line_offsets + ) + + target_wrapped_section_index = y - current_offset + # wrapped_section is the text that appears on a single y_offset within + # the TextArea. It's a potentially wrapped portion of a larger line from + # the original document. + target_wrapped_section = wrapped_lines[target_wrapped_section_index].plain + + # Get the column index within this wrapped section of the line + target_column_index = cell_width_to_column_index( + target_wrapped_section, x, tab_width + ) + + # Add the offsets from the wrapped sections above this one (from the same raw document line) + target_column_index += sum( + len(wrapped_section) + for wrapped_section in wrapped_lines[:target_wrapped_section_index] + ) + + return target_line_index, target_column_index def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index aa7997bd0a..e4ebc97b92 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -13,8 +13,8 @@ def test_wrap(): wrapped_document.wrap() assert wrapped_document.lines == [ - ["123 ", "4567"], - ["1234", "5"], + ["123 ", "4567"], # 0 # 1 + ["1234", "5"], # 2 # 3 ["1234", "5678", "9"], [""], ] @@ -74,11 +74,11 @@ def test_refresh_range_new_text_wrapped(): @pytest.mark.parametrize( - "offset,location", + "offset,location", # location is (row, column) [ - (Offset(0, 0), (0, 0)), - # (1, 0), - # (2, 1), + (Offset(x=0, y=0), (0, 0)), + (Offset(x=1, y=0), (0, 1)), + (Offset(x=2, y=1), (0, 6)), # (3, 1), # (4, 2), # (5, 2), From 56c79cf7b749371f0ec33bef09c1855e295c0799 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 13:11:53 +0000 Subject: [PATCH 027/150] Wrapping coordinate conversion clamping --- src/textual/document/_wrapped_document.py | 25 ++++++++++++----------- tests/document/test_wrapped_document.py | 21 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index ed026b40ba..1720cd869a 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -12,7 +12,7 @@ from textual._cells import cell_width_to_column_index from textual.document._document import DocumentBase, Location -from textual.geometry import Offset +from textual.geometry import Offset, clamp class WrappedDocument: @@ -115,20 +115,14 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: Returns: The line index corresponding to the given y-offset. """ - - def invalid_offset_error(): + x, y = offset + if x < 0 or y < 0: raise ValueError( f"No location exists at wrapped document offset {offset!r}. " f"Document is wrapped with width {self._width!r}." ) - x, y = offset - if y < 0: - invalid_offset_error() - current_offset = 0 - target_line_index = None - target_line_offsets = None for line_index, line_offsets in enumerate(self._wrap_offsets): next_offset = current_offset + len(line_offsets) + 1 if next_offset > y: @@ -136,10 +130,17 @@ def invalid_offset_error(): target_line_index = line_index target_line_offsets = line_offsets break - current_offset = next_offset - if target_line_index is None: - invalid_offset_error() + current_offset = next_offset + else: + # Offset doesn't match any line => land on bottom wrapped line + target_line_index = -1 + target_line_offsets = self._wrap_offsets[target_line_index] + wrapped_lines = Text(self.document[target_line_index]).divide( + target_line_offsets + ) + target_wrapped_section = wrapped_lines[-1].plain + return len(self._wrap_offsets) - 1, clamp(x, 0, len(target_wrapped_section)) # We've found the relevant line, now find the character by # looking at the character corresponding to the offset width. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index e4ebc97b92..fe7f40b30a 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -13,8 +13,8 @@ def test_wrap(): wrapped_document.wrap() assert wrapped_document.lines == [ - ["123 ", "4567"], # 0 # 1 - ["1234", "5"], # 2 # 3 + ["123 ", "4567"], + ["1234", "5"], ["1234", "5678", "9"], [""], ] @@ -79,11 +79,12 @@ def test_refresh_range_new_text_wrapped(): (Offset(x=0, y=0), (0, 0)), (Offset(x=1, y=0), (0, 1)), (Offset(x=2, y=1), (0, 6)), - # (3, 1), - # (4, 2), - # (5, 2), - # (6, 2), - # (7, 3), + (Offset(x=0, y=3), (1, 4)), + (Offset(x=1, y=3), (1, 5)), + (Offset(x=200, y=3), (1, 5)), + (Offset(x=0, y=6), (2, 8)), + (Offset(x=0, y=7), (3, 0)), # Clicking on the final, empty line + (Offset(x=0, y=1000), (3, 0)), ], ) def test_offset_to_location(offset, location): @@ -94,14 +95,14 @@ def test_offset_to_location(offset, location): assert wrapped_document.offset_to_location(offset, 2) == location -@pytest.mark.parametrize("offset", [-3, 1000]) -def test_offset_to_line_index_invalid_offset_raises_exception(offset): +@pytest.mark.parametrize("offset", [Offset(-3, 0), Offset(0, -10)]) +def test_offset_to_location_invalid_offset_raises_exception(offset): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() with pytest.raises(ValueError): - wrapped_document.offset_to_location(offset) + wrapped_document.offset_to_location(offset, 10) @pytest.mark.parametrize( From b0a7e51bfd1ae84e97a302c2e9558de16a823ade Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 14:45:16 +0000 Subject: [PATCH 028/150] Start on DocumentNavigator --- src/textual/document/_document_navigator.py | 93 +++++++++++++++++++++ src/textual/widgets/_text_area.py | 1 + 2 files changed, 94 insertions(+) create mode 100644 src/textual/document/_document_navigator.py diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py new file mode 100644 index 0000000000..1f2139be96 --- /dev/null +++ b/src/textual/document/_document_navigator.py @@ -0,0 +1,93 @@ +import re + +from textual.document._document import Location +from textual.document._wrapped_document import WrappedDocument + + +class DocumentNavigator: + """Cursor navigation in the TextArea is "wrapping-aware". + + Although the cursor location (the selection) is represented as a location + in the raw document, when you actually *move* the cursor, it must take wrapping + into account (otherwise things start to look really confusing to the user where + wrapping is involved). + + Your cursor visually moves through the wrapped version of the document, rather + than the raw document. So, for example, pressing down on the keyboard + may move your cursor to a position further along the current line, + rather than on to the next line in the raw document. + + The class manages this behaviour. + + Given a cursor location in the unwrapped document, and a cursor movement action, + this class can inform us of the destination the cursor will move to considering + the current wrapping width and document content. + + For this to work correctly, the wrapped_document and document must be synchronised. + This means that if you make an edit to the document, you *must* then update the + wrapped document, and *then* you may query the document navigator. + """ + + def __init__(self, wrapped_document: WrappedDocument) -> None: + """Create a CursorNavigator. + + Args: + wrapped_document: The WrappedDocument to be used when making navigation decisions. + """ + self._wrapped_document = wrapped_document + self._document = wrapped_document.document + + self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") + """Compiled regular expression for what we consider to be a 'word'.""" + + @property + def is_start_of_document_line(self, cursor: Location) -> bool: + """True if and only if the cursor is on the first line.""" + return cursor[0] == 0 + + @property + def cursor_at_last_line(self) -> bool: + """True if and only if the cursor is on the last line.""" + return self.selection.end[0] == self.document.line_count - 1 + + @property + def cursor_at_start_of_line(self) -> bool: + """True if and only if the cursor is at column 0.""" + return self.selection.end[1] == 0 + + @property + def cursor_at_end_of_line(self) -> bool: + """True if and only if the cursor is at the end of a row.""" + cursor_row, cursor_column = self.selection.end + row_length = len(self.document[cursor_row]) + cursor_at_end = cursor_column == row_length + return cursor_at_end + + @property + def cursor_at_start_of_text(self) -> bool: + """True if and only if the cursor is at location (0, 0)""" + return self.selection.end == (0, 0) + + @property + def cursor_at_end_of_text(self) -> bool: + """True if and only if the cursor is at the very end of the document.""" + return self.cursor_at_last_line and self.cursor_at_end_of_line + + def left(self, cursor: Location) -> Location: + if cursor == (0, 0): + return 0, 0 + + cursor_row, cursor_column = cursor + length_of_row_above = len(self._document[cursor_row - 1]) + target_row = cursor_row if cursor_column != 0 else cursor_row - 1 + target_column = cursor_column - 1 if cursor_column != 0 else length_of_row_above + return target_row, target_column + + def right(self, cursor: Location) -> Location: + # if + # return cursor + + cursor_row, cursor_column = cursor + target_row = cursor_row + 1 if self.cursor_at_end_of_line else cursor_row + target_column = 0 if self.cursor_at_end_of_line else cursor_column + 1 + return target_row, target_column diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 941835960c..7f350a276c 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -299,6 +299,7 @@ def __init__( self.indent_type: Literal["tabs", "spaces"] = "spaces" """Whether to indent using tabs or spaces.""" + # TODO - this can be removed after integrating the new DocumentNavigator. self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") """Compiled regular expression for what we consider to be a 'word'.""" From 84d51136a4ddff04608ee159cf54040f50052d8b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 15:55:22 +0000 Subject: [PATCH 029/150] Ensure consistent interface when wrapping enabled/disabled --- src/textual/document/_wrapped_document.py | 34 +++++++---- tests/document/test_wrapped_document.py | 72 ++++++++++++++++++++++- 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 1720cd869a..d2795b5913 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -12,7 +12,7 @@ from textual._cells import cell_width_to_column_index from textual.document._document import DocumentBase, Location -from textual.geometry import Offset, clamp +from textual.geometry import Offset class WrappedDocument: @@ -25,7 +25,7 @@ def __init__( Args: document: The document to wrap. - width: The cell-width to wrap at. + width: The cell-width to wrap at. 0 for no wrapping. """ self.document = document """The document wrapping is performed on.""" @@ -48,7 +48,8 @@ def wrap(self) -> None: width = self._width for line in self.document.lines: - append_wrap_offset(divide_line(line, width)) + wrap_offsets = divide_line(line, width) if width else [] + append_wrap_offset(wrap_offsets) self._wrap_offsets = new_wrap_offsets @@ -92,11 +93,12 @@ def refresh_range( new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append + width = self._width for line_index, line in enumerate(new_lines, start_row): - wrap_offsets = divide_line(line, self._width) + wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) - # Replace the range start->old with the new wrapped lines + # Replace the range start -> old with the new wrapped lines old_end_row, _ = old_end self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets @@ -113,15 +115,19 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: in the document. Returns: - The line index corresponding to the given y-offset. + The Location in the document corresponding to the given offset. """ x, y = offset if x < 0 or y < 0: - raise ValueError( - f"No location exists at wrapped document offset {offset!r}. " - f"Document is wrapped with width {self._width!r}." - ) + raise ValueError("Offset must be positive.") + if not self._width: + # No wrapping, so we directly map offset to location and clamp. + row_index = min(y, len(self._wrap_offsets) - 1) + column_index = min(x, len(self.document.get_line(row_index))) + return row_index, column_index + + # Find the line corresponding to the given y offset in the wrapped document. current_offset = 0 for line_index, line_offsets in enumerate(self._wrap_offsets): next_offset = current_offset + len(line_offsets) + 1 @@ -140,7 +146,13 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: target_line_offsets ) target_wrapped_section = wrapped_lines[-1].plain - return len(self._wrap_offsets) - 1, clamp(x, 0, len(target_wrapped_section)) + target_column_index = cell_width_to_column_index( + target_wrapped_section, x, tab_width + ) + target_column_index += sum( + len(wrapped_section) for wrapped_section in wrapped_lines[:-1] + ) + return len(self._wrap_offsets) - 1, target_column_index # We've found the relevant line, now find the character by # looking at the character corresponding to the offset width. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index fe7f40b30a..55e7a6d707 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -28,6 +28,19 @@ def test_wrap_empty_document(): assert wrapped_document.lines == [[""]] +def test_wrap_width_zero_no_wrapping(): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=0) + wrapped_document.wrap() + + assert wrapped_document.lines == [ + ["123 4567"], + ["12345"], + ["123456789"], + [""], + ] + + def test_refresh_range(): """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) @@ -73,6 +86,43 @@ def test_refresh_range_new_text_wrapped(): ] +def test_refresh_range_wrapping_at_previously_unavailable_range(): + """When we insert new content at the end of the document, ensure it wraps correctly.""" + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") + wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) + + assert wrapped_document.lines == [ + ["123 ", "4567"], + ["1234", "5"], + ["1234", "5678", "9"], + ["012 ", "3456"], + ["78 ", "9012", "3"], + ["45"], + ] + + +def test_refresh_range_wrapping_disabled_previously_unavailable_range(): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=0) # wrapping disabled + wrapped_document.wrap() + + edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") + wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) + + assert wrapped_document.lines == [ + ["123 4567"], + ["12345"], + ["123456789"], + ["012 3456"], + ["78 90123"], + ["45"], + ] + + @pytest.mark.parametrize( "offset,location", # location is (row, column) [ @@ -87,7 +137,7 @@ def test_refresh_range_new_text_wrapped(): (Offset(x=0, y=1000), (3, 0)), ], ) -def test_offset_to_location(offset, location): +def test_offset_to_location_wrapping_enabled(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) wrapped_document.wrap() @@ -95,6 +145,26 @@ def test_offset_to_location(offset, location): assert wrapped_document.offset_to_location(offset, 2) == location +@pytest.mark.parametrize( + "offset,location", # location is (row, column) + [ + (Offset(x=0, y=0), (0, 0)), + (Offset(x=1, y=0), (0, 1)), + (Offset(x=2, y=1), (1, 2)), + (Offset(x=0, y=3), (3, 0)), + (Offset(x=1, y=3), (3, 0)), + (Offset(x=200, y=3), (3, 0)), + (Offset(x=200, y=200), (3, 0)), # Clicking below the document + ], +) +def test_offset_to_location_wrapping_disabled(offset, location): + document = Document(SIMPLE_TEXT) + wrapped_document = WrappedDocument(document, width=0) + wrapped_document.wrap() + + assert wrapped_document.offset_to_location(offset, 4) == location + + @pytest.mark.parametrize("offset", [Offset(-3, 0), Offset(0, -10)]) def test_offset_to_location_invalid_offset_raises_exception(offset): document = Document(SIMPLE_TEXT) From 57adb2dfc420babca30eece08a6d3b4adeb991a1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 16:24:43 +0000 Subject: [PATCH 030/150] Update docstring --- src/textual/expand_tabs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 71751dd2c1..721da64d68 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -10,14 +10,18 @@ def get_tab_widths(line: str, tab_size: int = 4) -> list[tuple[str, int]]: """Splits a string line into tuples (str, int). + Each tuple represents a section of the line which precedes a tab character. + The string is the string text that appears before the tab character (excluding the tab). + The integer is the width that the tab character is expanded to. + Args: line: The text to expand tabs in. tab_size: Number of cells in a tab. Returns: - Each tuple represents a section of the line which precedes a tab character. - The string is the string text that appears before the tab character (excluding the tab). - The integer is the width that the tab character is expanded to. + A list of tuples representing the line split on tab characters, + and the widths of the tabs after tab expansion is applied. + """ parts: list[tuple[str, int]] = [] From 76cd4e8922308fdd512efd44e4157ff2024ac760 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 16:50:50 +0000 Subject: [PATCH 031/150] Code reuse in WrappedDocument --- src/textual/document/_wrapped_document.py | 63 ++++++++++------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index d2795b5913..36cf8da28f 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -121,6 +121,30 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: if x < 0 or y < 0: raise ValueError("Offset must be positive.") + def get_target_document_column(line_index: int, section_index: int) -> int: + # We've found the relevant line, now find the character by + # looking at the character corresponding to the offset width. + line_offsets = self._wrap_offsets[line_index] + wrapped_lines = Text(self.document[line_index]).divide(line_offsets) + + # wrapped_section is the text that appears on a single y_offset within + # the TextArea. It's a potentially wrapped portion of a larger line from + # the original document. + target_wrapped_section = wrapped_lines[section_index].plain + + # Get the column index within this wrapped section of the line + target_column_index = cell_width_to_column_index( + target_wrapped_section, x, tab_width + ) + + # Add the offsets from the wrapped sections above this one (from the same raw document line) + target_column_index += sum( + len(wrapped_section) + for wrapped_section in wrapped_lines[:section_index] + ) + + return target_column_index + if not self._width: # No wrapping, so we directly map offset to location and clamp. row_index = min(y, len(self._wrap_offsets) - 1) @@ -140,45 +164,12 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: current_offset = next_offset else: # Offset doesn't match any line => land on bottom wrapped line - target_line_index = -1 - target_line_offsets = self._wrap_offsets[target_line_index] - wrapped_lines = Text(self.document[target_line_index]).divide( - target_line_offsets - ) - target_wrapped_section = wrapped_lines[-1].plain - target_column_index = cell_width_to_column_index( - target_wrapped_section, x, tab_width - ) - target_column_index += sum( - len(wrapped_section) for wrapped_section in wrapped_lines[:-1] - ) - return len(self._wrap_offsets) - 1, target_column_index - - # We've found the relevant line, now find the character by - # looking at the character corresponding to the offset width. - wrapped_lines = Text(self.document[target_line_index]).divide( - target_line_offsets - ) - - target_wrapped_section_index = y - current_offset - # wrapped_section is the text that appears on a single y_offset within - # the TextArea. It's a potentially wrapped portion of a larger line from - # the original document. - target_wrapped_section = wrapped_lines[target_wrapped_section_index].plain + return len(self._wrap_offsets) - 1, get_target_document_column(-1, -1) - # Get the column index within this wrapped section of the line - target_column_index = cell_width_to_column_index( - target_wrapped_section, x, tab_width + return target_line_index, get_target_document_column( + target_line_index, y - current_offset ) - # Add the offsets from the wrapped sections above this one (from the same raw document line) - target_column_index += sum( - len(wrapped_section) - for wrapped_section in wrapped_lines[:target_wrapped_section_index] - ) - - return target_line_index, target_column_index - def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping should occur for the current document. From 028d95e7825b78861e1f09f4f9aa0861e3c8d6b9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 16:52:20 +0000 Subject: [PATCH 032/150] Simplification of wrapping coordinate conversion --- src/textual/document/_wrapped_document.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 36cf8da28f..dc4b0dda70 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -157,18 +157,13 @@ def get_target_document_column(line_index: int, section_index: int) -> int: next_offset = current_offset + len(line_offsets) + 1 if next_offset > y: # We've found the vertical offset. - target_line_index = line_index - target_line_offsets = line_offsets - break - + return line_index, get_target_document_column( + line_index, y - current_offset + ) current_offset = next_offset - else: - # Offset doesn't match any line => land on bottom wrapped line - return len(self._wrap_offsets) - 1, get_target_document_column(-1, -1) - return target_line_index, get_target_document_column( - target_line_index, y - current_offset - ) + # Offset doesn't match any line => land on bottom wrapped line + return len(self._wrap_offsets) - 1, get_target_document_column(-1, -1) def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping From 434a64abd51a97d99d9471a786489e84af80966b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 23 Nov 2023 17:00:11 +0000 Subject: [PATCH 033/150] Tidying some wrapping stuff, docstrings --- src/textual/document/_wrapped_document.py | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index dc4b0dda70..f5f5245d20 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -121,7 +121,24 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: if x < 0 or y < 0: raise ValueError("Offset must be positive.") - def get_target_document_column(line_index: int, section_index: int) -> int: + if not self._width: + # No wrapping, so we directly map offset to location and clamp. + row_index = min(y, len(self._wrap_offsets) - 1) + column_index = min(x, len(self.document.get_line(row_index))) + return row_index, column_index + + def get_target_document_column(line_index: int, y_offset: int) -> int: + """Given a line index and the vertical offset within that + line, return the corresponding column index in the raw document. + + Args: + line_index: The index of the line in the document. + y_offset: The y-offset within the wrapped version of the line. + + Returns: + The column index corresponding to the line index and y offset. + """ + # We've found the relevant line, now find the character by # looking at the character corresponding to the offset width. line_offsets = self._wrap_offsets[line_index] @@ -130,7 +147,7 @@ def get_target_document_column(line_index: int, section_index: int) -> int: # wrapped_section is the text that appears on a single y_offset within # the TextArea. It's a potentially wrapped portion of a larger line from # the original document. - target_wrapped_section = wrapped_lines[section_index].plain + target_wrapped_section = wrapped_lines[y_offset].plain # Get the column index within this wrapped section of the line target_column_index = cell_width_to_column_index( @@ -139,18 +156,11 @@ def get_target_document_column(line_index: int, section_index: int) -> int: # Add the offsets from the wrapped sections above this one (from the same raw document line) target_column_index += sum( - len(wrapped_section) - for wrapped_section in wrapped_lines[:section_index] + len(wrapped_section) for wrapped_section in wrapped_lines[:y_offset] ) return target_column_index - if not self._width: - # No wrapping, so we directly map offset to location and clamp. - row_index = min(y, len(self._wrap_offsets) - 1) - column_index = min(x, len(self.document.get_line(row_index))) - return row_index, column_index - # Find the line corresponding to the given y offset in the wrapped document. current_offset = 0 for line_index, line_offsets in enumerate(self._wrap_offsets): From ee27c73632fc70963271a2e0a3f96bf07deeb413 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 27 Nov 2023 10:31:14 +0000 Subject: [PATCH 034/150] Check locations at start of wrapped/document line --- src/textual/document/_document_navigator.py | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 1f2139be96..6f323665f8 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -40,10 +40,27 @@ def __init__(self, wrapped_document: WrappedDocument) -> None: self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") """Compiled regular expression for what we consider to be a 'word'.""" - @property - def is_start_of_document_line(self, cursor: Location) -> bool: - """True if and only if the cursor is on the first line.""" - return cursor[0] == 0 + def is_start_of_document_line(self, location: Location) -> bool: + """True when the location is at the start of the first document line. + + Args: + location: The location to check. + + Returns: + True if the location is at column index 0. + """ + return location[0] == 0 + + def is_start_of_wrapped_line(self, location: Location) -> bool: + """True when the location is at the start of the first wrapped line. + + Args: + location: The location to check. + + Returns: + True if the location is at column index 0. + """ + return self.is_start_of_document_line(location) @property def cursor_at_last_line(self) -> bool: From 2aad184f3fc0162c1d5a943219cc289c0580b4e5 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 27 Nov 2023 15:09:48 +0000 Subject: [PATCH 035/150] Checking cursor position in wrapped context utilities --- src/textual/document/_document_navigator.py | 113 +++++++++++++++----- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 6f323665f8..c46ae521c3 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -1,4 +1,6 @@ import re +from bisect import bisect_left +from typing import Any, Sequence from textual.document._document import Location from textual.document._wrapped_document import WrappedDocument @@ -49,7 +51,7 @@ def is_start_of_document_line(self, location: Location) -> bool: Returns: True if the location is at column index 0. """ - return location[0] == 0 + return location[1] == 0 def is_start_of_wrapped_line(self, location: Location) -> bool: """True when the location is at the start of the first wrapped line. @@ -60,35 +62,74 @@ def is_start_of_wrapped_line(self, location: Location) -> bool: Returns: True if the location is at column index 0. """ - return self.is_start_of_document_line(location) - - @property - def cursor_at_last_line(self) -> bool: - """True if and only if the cursor is on the last line.""" - return self.selection.end[0] == self.document.line_count - 1 - - @property - def cursor_at_start_of_line(self) -> bool: - """True if and only if the cursor is at column 0.""" - return self.selection.end[1] == 0 - - @property - def cursor_at_end_of_line(self) -> bool: - """True if and only if the cursor is at the end of a row.""" - cursor_row, cursor_column = self.selection.end - row_length = len(self.document[cursor_row]) - cursor_at_end = cursor_column == row_length - return cursor_at_end - - @property - def cursor_at_start_of_text(self) -> bool: + if self.is_start_of_document_line(location): + return True + + row, column = location + wrap_offsets = self._wrapped_document.get_offsets(row) + return index(wrap_offsets, column) != -1 + + def is_end_of_document_line(self, location: Location) -> bool: + """True if the location is at the end of a line in the document.""" + row, column = location + row_length = len(self._document[row]) + return column == row_length + + def is_end_of_wrapped_line(self, location: Location) -> bool: + """True if the location is at the end of a wrapped line.""" + if self.is_end_of_document_line(location): + return True + + row, column = location + wrap_offsets = self._wrapped_document.get_offsets(row) + return index(wrap_offsets, column - 1) != -1 + + def is_first_document_line(self, location: Location) -> bool: + return location[0] == 0 + + def is_first_wrapped_line(self, location: Location) -> bool: + # TODO: Check the less than/equal to thing. + # Ensure that the column index of the location is less (or equal to?!?!) than + # the first value in the wrap offsets. + if not self.is_first_document_line(location): + return False + + row, column = location + wrap_offsets = self._wrapped_document.get_offsets(row) + + if not wrap_offsets: + return True + + if column <= wrap_offsets[0]: + return True + return False + + def is_last_document_line(self, location: Location) -> bool: + """True when the location is on the last line of the document.""" + return location[0] == self._document.line_count - 1 + + def is_last_wrapped_line(self, location: Location) -> bool: + if not self.is_last_document_line(location): + return False + + row, column = location + wrap_offsets = self._wrapped_document.get_offsets(row) + + if not wrap_offsets: + return True + + if column > wrap_offsets[-1]: + return True + return False + + def is_start_of_document(self, location: Location) -> bool: """True if and only if the cursor is at location (0, 0)""" - return self.selection.end == (0, 0) + return location == (0, 0) - @property - def cursor_at_end_of_text(self) -> bool: - """True if and only if the cursor is at the very end of the document.""" - return self.cursor_at_last_line and self.cursor_at_end_of_line + def is_end_of_document(self, location: Location) -> bool: + return self.is_last_document_line(location) and self.is_end_of_document_line( + location + ) def left(self, cursor: Location) -> Location: if cursor == (0, 0): @@ -108,3 +149,19 @@ def right(self, cursor: Location) -> Location: target_row = cursor_row + 1 if self.cursor_at_end_of_line else cursor_row target_column = 0 if self.cursor_at_end_of_line else cursor_column + 1 return target_row, target_column + + +def index(sequence: Sequence, value: Any) -> int: + """Locate the leftmost item in the sequence equal to value via bisection. + + Args: + sequence: The sequence to search in. + value: The value to find. + + Returns: + The index of the value, or -1 if the value is not found in the sequence. + """ + insert_index = bisect_left(sequence, value) + if insert_index != len(sequence) and sequence[insert_index] == value: + return insert_index + return -1 From f4da5d8aed9a8948cffe5c698c0e41d0479c6ea2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 27 Nov 2023 15:29:04 +0000 Subject: [PATCH 036/150] Left and right navigation --- src/textual/document/_document_navigator.py | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index c46ae521c3..c93e1c60fd 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -131,23 +131,23 @@ def is_end_of_document(self, location: Location) -> bool: location ) - def left(self, cursor: Location) -> Location: - if cursor == (0, 0): + def left(self, location: Location) -> Location: + if location == (0, 0): return 0, 0 - cursor_row, cursor_column = cursor - length_of_row_above = len(self._document[cursor_row - 1]) - target_row = cursor_row if cursor_column != 0 else cursor_row - 1 - target_column = cursor_column - 1 if cursor_column != 0 else length_of_row_above + row, column = location + length_of_row_above = len(self._document[row - 1]) + target_row = row if column != 0 else row - 1 + target_column = column - 1 if column != 0 else length_of_row_above return target_row, target_column - def right(self, cursor: Location) -> Location: - # if - # return cursor - - cursor_row, cursor_column = cursor - target_row = cursor_row + 1 if self.cursor_at_end_of_line else cursor_row - target_column = 0 if self.cursor_at_end_of_line else cursor_column + 1 + def right(self, location: Location) -> Location: + if self.is_end_of_document(location): + return location + row, cursor_column = location + is_end_of_line = self.is_end_of_document_line(location) + target_row = row + 1 if is_end_of_line else row + target_column = 0 if is_end_of_line else cursor_column + 1 return target_row, target_column From f033df09a10a46b4ee08dae7fe9adff47e829da2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 28 Nov 2023 10:17:52 +0000 Subject: [PATCH 037/150] Begin implementing up --- src/textual/document/_document_navigator.py | 34 ++++++++- src/textual/document/_wrapped_document.py | 84 ++++++++++++--------- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index c93e1c60fd..6f0f18ef51 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -144,12 +144,42 @@ def left(self, location: Location) -> Location: def right(self, location: Location) -> Location: if self.is_end_of_document(location): return location - row, cursor_column = location + row, column = location is_end_of_line = self.is_end_of_document_line(location) target_row = row + 1 if is_end_of_line else row - target_column = 0 if is_end_of_line else cursor_column + 1 + target_column = 0 if is_end_of_line else column + 1 return target_row, target_column + def up(self, location: Location) -> Location: + """Get the location up from the given location in the wrapped document.""" + + # Moving up from a position on the first visual line moves us to the start. + if self.is_first_wrapped_line(location): + return 0, 0 + + # Get the wrap offsets of the current line. + row, column = location + wrap_offsets = self._wrapped_document.get_offsets(row) + + # We need to find the insertion point to determine which section index we're + # on within the current line. When we know the section index, we can use it + # to find the section which sits above it. + if wrap_offsets: + section_index = bisect_left(wrap_offsets, column) + + # Find the cursor offset *within* the section + + # Convert that cursor offset to a cell (visual) offset + + # Get the section that sits above this one - note that if the section index + # is 0, then we'll have to get section at index -1 from the line above. + + # If we're on the first section of a wrapped line, then we need to retrieve + # the last section of the line before it to calculate where the cursor + # should move to. + + current_section = self._wrapped_document.get_target_document_column() + def index(sequence: Sequence, value: Any) -> int: """Locate the leftmost item in the sequence equal to value via bisection. diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index f5f5245d20..c2194c738b 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -127,53 +127,63 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: column_index = min(x, len(self.document.get_line(row_index))) return row_index, column_index - def get_target_document_column(line_index: int, y_offset: int) -> int: - """Given a line index and the vertical offset within that - line, return the corresponding column index in the raw document. - - Args: - line_index: The index of the line in the document. - y_offset: The y-offset within the wrapped version of the line. - - Returns: - The column index corresponding to the line index and y offset. - """ - - # We've found the relevant line, now find the character by - # looking at the character corresponding to the offset width. - line_offsets = self._wrap_offsets[line_index] - wrapped_lines = Text(self.document[line_index]).divide(line_offsets) - - # wrapped_section is the text that appears on a single y_offset within - # the TextArea. It's a potentially wrapped portion of a larger line from - # the original document. - target_wrapped_section = wrapped_lines[y_offset].plain - - # Get the column index within this wrapped section of the line - target_column_index = cell_width_to_column_index( - target_wrapped_section, x, tab_width - ) - - # Add the offsets from the wrapped sections above this one (from the same raw document line) - target_column_index += sum( - len(wrapped_section) for wrapped_section in wrapped_lines[:y_offset] - ) - - return target_column_index - # Find the line corresponding to the given y offset in the wrapped document. current_offset = 0 for line_index, line_offsets in enumerate(self._wrap_offsets): next_offset = current_offset + len(line_offsets) + 1 if next_offset > y: # We've found the vertical offset. - return line_index, get_target_document_column( - line_index, y - current_offset + return line_index, self.get_target_document_column( + line_index, x, y - current_offset, tab_width ) current_offset = next_offset # Offset doesn't match any line => land on bottom wrapped line - return len(self._wrap_offsets) - 1, get_target_document_column(-1, -1) + return len(self._wrap_offsets) - 1, self.get_target_document_column( + -1, x, -1, tab_width + ) + + def get_target_document_column( + self, + line_index: int, + x_offset: int, + y_offset: int, + tab_width: int, + ) -> int: + """Given a line index and the vertical offset within that + line, return the corresponding column index in the raw document. + + Args: + line_index: The index of the line in the document. + x_offset: The x-offset within the wrapped line. + y_offset: The y-offset within the wrapped line (supports negative indexing). + tab_width: The size of the tab stop. + + Returns: + The column index corresponding to the line index and y offset. + """ + + # We've found the relevant line, now find the character by + # looking at the character corresponding to the offset width. + line_offsets = self._wrap_offsets[line_index] + wrapped_lines = Text(self.document[line_index]).divide(line_offsets) + + # wrapped_section is the text that appears on a single y_offset within + # the TextArea. It's a potentially wrapped portion of a larger line from + # the original document. + target_wrapped_section = wrapped_lines[y_offset].plain + + # Get the column index within this wrapped section of the line + target_column_index = cell_width_to_column_index( + target_wrapped_section, x_offset, tab_width + ) + + # Add the offsets from the wrapped sections above this one (from the same raw document line) + target_column_index += sum( + len(wrapped_section) for wrapped_section in wrapped_lines[:y_offset] + ) + + return target_column_index def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping From 9a180ea1d0623bb61be35673e79e18d43209bb87 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 29 Nov 2023 14:20:30 +0000 Subject: [PATCH 038/150] Implement `up` in document navigator --- src/textual/document/_document_navigator.py | 54 ++++++++++++++------- src/textual/document/_wrapped_document.py | 12 +++-- tests/document/test_wrapped_document.py | 8 +++ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 6f0f18ef51..94cec03d26 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -1,7 +1,8 @@ import re -from bisect import bisect_left +from bisect import bisect_left, bisect_right from typing import Any, Sequence +from textual._cells import cell_len from textual.document._document import Location from textual.document._wrapped_document import WrappedDocument @@ -42,6 +43,10 @@ def __init__(self, wrapped_document: WrappedDocument) -> None: self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") """Compiled regular expression for what we consider to be a 'word'.""" + self.last_x_offset = 0 + """Remembers the last x offset (cell width) the cursor was moved horizontally to, + so that it can be restored on vertical movement where possible.""" + def is_start_of_document_line(self, location: Location) -> bool: """True when the location is at the start of the first document line. @@ -150,7 +155,7 @@ def right(self, location: Location) -> Location: target_column = 0 if is_end_of_line else column + 1 return target_row, target_column - def up(self, location: Location) -> Location: + def up(self, location: Location, tab_width: int) -> Location: """Get the location up from the given location in the wrapped document.""" # Moving up from a position on the first visual line moves us to the start. @@ -158,27 +163,37 @@ def up(self, location: Location) -> Location: return 0, 0 # Get the wrap offsets of the current line. - row, column = location - wrap_offsets = self._wrapped_document.get_offsets(row) + line_index, column_index = location + wrap_offsets = self._wrapped_document.get_offsets(line_index) + section_start_columns = [0, *wrap_offsets] # We need to find the insertion point to determine which section index we're # on within the current line. When we know the section index, we can use it # to find the section which sits above it. - if wrap_offsets: - section_index = bisect_left(wrap_offsets, column) - - # Find the cursor offset *within* the section + section_index = find_leftmost_greater_than(wrap_offsets, column_index) + offset_within_section = column_index - section_start_columns[section_index] + wrapped_line = self._wrapped_document.get_wrapped_line(line_index) + section = wrapped_line[section_index] # Convert that cursor offset to a cell (visual) offset - - # Get the section that sits above this one - note that if the section index - # is 0, then we'll have to get section at index -1 from the line above. - - # If we're on the first section of a wrapped line, then we need to retrieve - # the last section of the line before it to calculate where the cursor - # should move to. - - current_section = self._wrapped_document.get_target_document_column() + current_visual_offset = cell_len(section[:offset_within_section]) + + # TODO - account for last_x_offset in both branches here. + if section_index == 0: + # Get the last section from the line above, and find where to move in it. + target_row = line_index - 1 + target_column = self._wrapped_document.get_target_document_column( + target_row, current_visual_offset, -1, tab_width + ) + target_location = target_row, target_column + else: + # Stay on the same document line, but move backwards. + target_column = self._wrapped_document.get_target_document_column( + line_index, current_visual_offset, section_index - 1, tab_width + ) + target_location = line_index, target_column + + return target_location def index(sequence: Sequence, value: Any) -> int: @@ -195,3 +210,8 @@ def index(sequence: Sequence, value: Any) -> int: if insert_index != len(sequence) and sequence[insert_index] == value: return insert_index return -1 + + +def find_leftmost_greater_than(sequence: Sequence, value: Any) -> int: + """Find the index of the leftmost value greater than x.""" + return bisect_right(sequence, value) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index c2194c738b..8bdfce2e16 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -150,7 +150,7 @@ def get_target_document_column( y_offset: int, tab_width: int, ) -> int: - """Given a line index and the vertical offset within that + """Given a line index and the offsets within the wrapped version of that line, return the corresponding column index in the raw document. Args: @@ -165,13 +165,12 @@ def get_target_document_column( # We've found the relevant line, now find the character by # looking at the character corresponding to the offset width. - line_offsets = self._wrap_offsets[line_index] - wrapped_lines = Text(self.document[line_index]).divide(line_offsets) + wrapped_lines = self.get_wrapped_line(line_index) # wrapped_section is the text that appears on a single y_offset within # the TextArea. It's a potentially wrapped portion of a larger line from # the original document. - target_wrapped_section = wrapped_lines[y_offset].plain + target_wrapped_section = wrapped_lines[y_offset] # Get the column index within this wrapped section of the line target_column_index = cell_width_to_column_index( @@ -185,6 +184,11 @@ def get_target_document_column( return target_column_index + def get_wrapped_line(self, line_index: int) -> list[str]: + line_offsets = self._wrap_offsets[line_index] + wrapped_lines = Text(self.document[line_index]).divide(line_offsets) + return [line.plain for line in wrapped_lines] + def get_offsets(self, line_index: int) -> list[int]: """Given a line index, get the offsets within that line where wrapping should occur for the current document. diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 55e7a6d707..bb6103d07c 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -191,6 +191,14 @@ def test_get_offsets(line_index, offsets): assert wrapped_document.get_offsets(line_index) == offsets +def test_get_offsets_no_wrapping(): + document = Document("abc") + wrapped_document = WrappedDocument(document, width=4) + wrapped_document.wrap() + + assert wrapped_document.get_offsets(0) == [] + + @pytest.mark.parametrize("line_index", [-4, 10000]) def test_get_offsets_invalid_line_index(line_index): document = Document(SIMPLE_TEXT) From daefc7eba494d786af57051b4f5b9042cb68ac49 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 29 Nov 2023 16:54:53 +0000 Subject: [PATCH 039/150] Implement clamping --- src/textual/_cells.py | 2 +- src/textual/document/_document_navigator.py | 21 ++++++++++- tests/document/test_document_navigator.py | 39 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/document/test_document_navigator.py diff --git a/src/textual/_cells.py b/src/textual/_cells.py index 716faaed66..35f4374ea0 100644 --- a/src/textual/_cells.py +++ b/src/textual/_cells.py @@ -41,4 +41,4 @@ def cell_width_to_column_index(line: str, cell_width: int, tab_width: int) -> in column_index += 1 - return len(line) + return len(line) - 1 diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 94cec03d26..06c63fdca1 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -5,6 +5,7 @@ from textual._cells import cell_len from textual.document._document import Location from textual.document._wrapped_document import WrappedDocument +from textual.geometry import clamp class DocumentNavigator: @@ -193,7 +194,25 @@ def up(self, location: Location, tab_width: int) -> Location: ) target_location = line_index, target_column - return target_location + return self.clamp_reachable(target_location) + + def clamp_reachable(self, location: Location) -> Location: + """Given a location, return the nearest location that corresponds to a + reachable location in the document. + + Args: + location: A location. + + Returns: + The nearest reachable location in the document. + """ + document = self._document + row, column = location + clamped_row = clamp(row, 0, document.line_count - 1) + + row_text = self._document[clamped_row] + clamped_column = clamp(column, 0, len(row_text)) + return clamped_row, clamped_column def index(sequence: Sequence, value: Any) -> int: diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py new file mode 100644 index 0000000000..8f86ccf81d --- /dev/null +++ b/tests/document/test_document_navigator.py @@ -0,0 +1,39 @@ +import pytest + +from textual.document._document import Document +from textual.document._document_navigator import DocumentNavigator +from textual.document._wrapped_document import WrappedDocument + +TEXT = """\ +01 3456 +0123""" + +# wrapped width = 4: +# line_index | wrapped_lines +# 0 | 01_ +# | 3456 +# 1 | 0123 + + +def make_navigator(text, width): + document = Document(text) + wrapped_document = WrappedDocument(document, width) + wrapped_document.wrap() + navigator = DocumentNavigator(wrapped_document) + return navigator + + +@pytest.mark.parametrize( + "start,end", + [ + [(0, 0), (0, 0)], + [(0, 1), (0, 0)], + [(0, 2), (0, 0)], + [(0, 3), (0, 0)], + [(0, 4), (0, 1)], + [(0, 5), (0, 2)], + [(0, 6), (0, 2)], # it should clamp to the last valid index + ], +) +def test_up(start, end): + assert make_navigator(TEXT, 4).up(start, 4) == end From 6779a9b2c13b86879365e6449980c6cc3df22bd7 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 29 Nov 2023 17:05:25 +0000 Subject: [PATCH 040/150] More tests for navigating through wrapped content --- tests/document/test_document_navigator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 8f86ccf81d..c45f2bd0c1 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -32,7 +32,10 @@ def make_navigator(text, width): [(0, 3), (0, 0)], [(0, 4), (0, 1)], [(0, 5), (0, 2)], - [(0, 6), (0, 2)], # it should clamp to the last valid index + [(0, 6), (0, 2)], # clamps to valid index + [(0, 7), (0, 2)], # clamps to the last valid index + [(1, 0), (0, 3)], + [(1, 1), (0, 4)], ], ) def test_up(start, end): From 5a1ced563da1c9c5b64398881e71fdc7d62b6280 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 29 Nov 2023 17:19:12 +0000 Subject: [PATCH 041/150] Improve test data --- tests/document/test_document_navigator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index c45f2bd0c1..4f11736bc4 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -6,13 +6,13 @@ TEXT = """\ 01 3456 -0123""" +01234""" # wrapped width = 4: # line_index | wrapped_lines # 0 | 01_ # | 3456 -# 1 | 0123 +# 1 | 01234 def make_navigator(text, width): From 7d9cb5d0db8a2c744143155b9c72ddcd1b6aa08b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 30 Nov 2023 10:43:19 +0000 Subject: [PATCH 042/150] Document navigator down --- src/textual/document/_document_navigator.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 06c63fdca1..4375ea45df 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -196,6 +196,19 @@ def up(self, location: Location, tab_width: int) -> Location: return self.clamp_reachable(target_location) + def down(self, location: Location, tab_width: int) -> Location: + """Given a location in the raw document, return the raw document + location corresponding to moving down in the wrapped representation + of the document. + + Args: + location: The location in the raw document. + tab_width: The width of the tab stops. + + Returns: + The location which is *visually* below the given location. + """ + def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a reachable location in the document. From 7b1467b58384a313fa217d2f2d769489dacfd2ae Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 30 Nov 2023 11:02:30 +0000 Subject: [PATCH 043/150] Implement moving down --- src/textual/document/_document_navigator.py | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 4375ea45df..cd9bed849d 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -208,6 +208,37 @@ def down(self, location: Location, tab_width: int) -> Location: Returns: The location which is *visually* below the given location. """ + line_index, column_index = location + document = self._document + + if self.is_last_document_line(location): + return line_index, len(document[line_index]) + + wrap_offsets = self._wrapped_document.get_offsets(line_index) + section_start_columns = [0, *wrap_offsets] + section_index = find_leftmost_greater_than(wrap_offsets, column_index) + offset_within_section = column_index - section_start_columns[section_index] + wrapped_line = self._wrapped_document.get_wrapped_line(line_index) + section = wrapped_line[section_index] + current_visual_offset = cell_len(section[:offset_within_section]) + + # If we're at the last section/row of a wrapped line + if section_index == len(wrapped_line) - 1: + # Go to the first section of the line below. + target_row = line_index + 1 + target_column = self._wrapped_document.get_target_document_column( + target_row, current_visual_offset, 0, tab_width + ) + target_location = target_row, target_column + else: + # Stay on the same document line, but move forwards to + # the location on the section below with the same visual offset. + target_column = self._wrapped_document.get_target_document_column( + line_index, current_visual_offset, section_index + 1, tab_width + ) + target_location = line_index, target_column + + return self.clamp_reachable(target_location) def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a From 185079cc2fe2b8669fbd2b5011c9b713e00b980c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 30 Nov 2023 11:33:57 +0000 Subject: [PATCH 044/150] Testing moving down --- src/textual/document/_document_navigator.py | 4 ++++ tests/document/test_document_navigator.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index cd9bed849d..2a4935632a 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -229,6 +229,10 @@ def down(self, location: Location, tab_width: int) -> Location: target_column = self._wrapped_document.get_target_document_column( target_row, current_visual_offset, 0, tab_width ) + # TODO - need to decide what to do with the return at the + # bottom of cell_width_to_column_index - we need to be able + # to move to len(final_section) when on final_section. + # However, while on any other section, we must not move beyond. target_location = target_row, target_column else: # Stay on the same document line, but move forwards to diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 4f11736bc4..e5bbe07673 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -8,6 +8,7 @@ 01 3456 01234""" + # wrapped width = 4: # line_index | wrapped_lines # 0 | 01_ @@ -40,3 +41,20 @@ def make_navigator(text, width): ) def test_up(start, end): assert make_navigator(TEXT, 4).up(start, 4) == end + + +@pytest.mark.parametrize( + "start,end", + [ + # [(0, 0), (0, 3)], + # [(0, 1), (0, 4)], + # [(0, 2), (0, 5)], + # [(0, 3), (1, 0)], + # [(0, 4), (1, 1)], + # [(0, 5), (1, 2)], + # [(0, 6), (1, 3)], + [(0, 7), (1, 4)], + ], +) +def test_down(start, end): + assert make_navigator(TEXT, 4).down(start, 4) == end From 0e72b35b1267aa82568e7b4b2aaa9f366dc6c7aa Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 11 Dec 2023 10:47:23 +0000 Subject: [PATCH 045/150] Remove redundant function --- src/textual/document/_document_navigator.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 06c63fdca1..bf9e18af23 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -171,7 +171,7 @@ def up(self, location: Location, tab_width: int) -> Location: # We need to find the insertion point to determine which section index we're # on within the current line. When we know the section index, we can use it # to find the section which sits above it. - section_index = find_leftmost_greater_than(wrap_offsets, column_index) + section_index = bisect_right(wrap_offsets, column_index) offset_within_section = column_index - section_start_columns[section_index] wrapped_line = self._wrapped_document.get_wrapped_line(line_index) section = wrapped_line[section_index] @@ -229,8 +229,3 @@ def index(sequence: Sequence, value: Any) -> int: if insert_index != len(sequence) and sequence[insert_index] == value: return insert_index return -1 - - -def find_leftmost_greater_than(sequence: Sequence, value: Any) -> int: - """Find the index of the leftmost value greater than x.""" - return bisect_right(sequence, value) From 024d1b68c7d246eecdacdb4b4aabfb970464a905 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 11 Dec 2023 14:16:54 +0000 Subject: [PATCH 046/150] Adding wrapped text area movement module docs, correcting off by one --- src/textual/_cells.py | 2 +- src/textual/document/_document_navigator.py | 66 ++++++++++++--------- src/textual/document/_wrapped_document.py | 25 +++++--- tests/document/test_document_navigator.py | 21 ++++--- 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/textual/_cells.py b/src/textual/_cells.py index 35f4374ea0..716faaed66 100644 --- a/src/textual/_cells.py +++ b/src/textual/_cells.py @@ -41,4 +41,4 @@ def cell_width_to_column_index(line: str, cell_width: int, tab_width: int) -> in column_index += 1 - return len(line) - 1 + return len(line) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 3ed0734575..4bc982eb1b 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -1,3 +1,35 @@ +"""Cursor navigation in the TextArea is "wrapping-aware". + +Although the cursor location (the selection) is represented as a location +in the raw document, when you actually *move* the cursor, it must take wrapping +into account (otherwise things start to look really confusing to the user where +wrapping is involved). + +Your cursor visually moves through the wrapped version of the document, rather +than the raw document. So, for example, pressing down on the keyboard +may move your cursor to a position further along the current line, +rather than on to the next line in the raw document. + +The class manages this behaviour. + +Given a cursor location in the unwrapped document, and a cursor movement action, +this class can inform us of the destination the cursor will move to considering +the current wrapping width and document content. + +For this to work correctly, the wrapped_document and document must be synchronised. +This means that if you make an edit to the document, you *must* then update the +wrapped document, and *then* you may query the document navigator. + +Naming conventions: + +A line "wrapped section" refers to a portion of the line accounting for wrapping. +For example the line "ABCDEF" when wrapped at width 3 will result in 2 sections: +"ABC" and "DEF". + +A "wrap offset" is an integer representing the index at which wrapping occurs in a line. +In "ABCDEF" with wrapping at width 3, there is a single wrap offset of 3. +""" + import re from bisect import bisect_left, bisect_right from typing import Any, Sequence @@ -9,29 +41,6 @@ class DocumentNavigator: - """Cursor navigation in the TextArea is "wrapping-aware". - - Although the cursor location (the selection) is represented as a location - in the raw document, when you actually *move* the cursor, it must take wrapping - into account (otherwise things start to look really confusing to the user where - wrapping is involved). - - Your cursor visually moves through the wrapped version of the document, rather - than the raw document. So, for example, pressing down on the keyboard - may move your cursor to a position further along the current line, - rather than on to the next line in the raw document. - - The class manages this behaviour. - - Given a cursor location in the unwrapped document, and a cursor movement action, - this class can inform us of the destination the cursor will move to considering - the current wrapping width and document content. - - For this to work correctly, the wrapped_document and document must be synchronised. - This means that if you make an edit to the document, you *must* then update the - wrapped document, and *then* you may query the document navigator. - """ - def __init__(self, wrapped_document: WrappedDocument) -> None: """Create a CursorNavigator. @@ -173,7 +182,7 @@ def up(self, location: Location, tab_width: int) -> Location: # to find the section which sits above it. section_index = bisect_right(wrap_offsets, column_index) offset_within_section = column_index - section_start_columns[section_index] - wrapped_line = self._wrapped_document.get_wrapped_line(line_index) + wrapped_line = self._wrapped_document.get_sections(line_index) section = wrapped_line[section_index] # Convert that cursor offset to a cell (visual) offset @@ -189,6 +198,9 @@ def up(self, location: Location, tab_width: int) -> Location: target_location = target_row, target_column else: # Stay on the same document line, but move backwards. + # Since the section above could be shorter, we need to clamp the column + # to a valid value. + target_column = self._wrapped_document.get_target_document_column( line_index, current_visual_offset, section_index - 1, tab_width ) @@ -216,9 +228,9 @@ def down(self, location: Location, tab_width: int) -> Location: wrap_offsets = self._wrapped_document.get_offsets(line_index) section_start_columns = [0, *wrap_offsets] - section_index = find_leftmost_greater_than(wrap_offsets, column_index) + section_index = bisect_right(wrap_offsets, column_index) offset_within_section = column_index - section_start_columns[section_index] - wrapped_line = self._wrapped_document.get_wrapped_line(line_index) + wrapped_line = self._wrapped_document.get_sections(line_index) section = wrapped_line[section_index] current_visual_offset = cell_len(section[:offset_within_section]) @@ -242,7 +254,7 @@ def down(self, location: Location, tab_width: int) -> Location: ) target_location = line_index, target_column - return self.clamp_reachable(target_location) + return target_location def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 8bdfce2e16..72cde89a26 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -165,26 +165,33 @@ def get_target_document_column( # We've found the relevant line, now find the character by # looking at the character corresponding to the offset width. - wrapped_lines = self.get_wrapped_line(line_index) + sections = self.get_sections(line_index) # wrapped_section is the text that appears on a single y_offset within # the TextArea. It's a potentially wrapped portion of a larger line from # the original document. - target_wrapped_section = wrapped_lines[y_offset] + target_section = sections[y_offset] - # Get the column index within this wrapped section of the line - target_column_index = cell_width_to_column_index( - target_wrapped_section, x_offset, tab_width + # Add the offsets from the wrapped sections above this one (from the same raw document line) + target_section_start = sum( + len(wrapped_section) for wrapped_section in sections[:y_offset] ) - # Add the offsets from the wrapped sections above this one (from the same raw document line) - target_column_index += sum( - len(wrapped_section) for wrapped_section in wrapped_lines[:y_offset] + # Get the column index within this wrapped section of the line + target_column_index = target_section_start + cell_width_to_column_index( + target_section, x_offset, tab_width ) + # If we're on the final section of a line, the cursor can legally rest beyond the end by a single cell. + # Otherwise, we'll need to ensure that we're keeping the cursor within the bounds of the target section. + if y_offset != len(sections) - 1 and y_offset != -1: + target_column_index = min( + target_column_index, target_section_start + len(target_section) - 1 + ) + return target_column_index - def get_wrapped_line(self, line_index: int) -> list[str]: + def get_sections(self, line_index: int) -> list[str]: line_offsets = self._wrap_offsets[line_index] wrapped_lines = Text(self.document[line_index]).divide(line_offsets) return [line.plain for line in wrapped_lines] diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index e5bbe07673..7087930c7d 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -13,7 +13,8 @@ # line_index | wrapped_lines # 0 | 01_ # | 3456 -# 1 | 01234 +# 1 | 0123 +# | 4 def make_navigator(text, width): @@ -37,6 +38,7 @@ def make_navigator(text, width): [(0, 7), (0, 2)], # clamps to the last valid index [(1, 0), (0, 3)], [(1, 1), (0, 4)], + [(1, 5), (1, 1)], ], ) def test_up(start, end): @@ -46,14 +48,15 @@ def test_up(start, end): @pytest.mark.parametrize( "start,end", [ - # [(0, 0), (0, 3)], - # [(0, 1), (0, 4)], - # [(0, 2), (0, 5)], - # [(0, 3), (1, 0)], - # [(0, 4), (1, 1)], - # [(0, 5), (1, 2)], - # [(0, 6), (1, 3)], - [(0, 7), (1, 4)], + [(0, 0), (0, 3)], + [(0, 1), (0, 4)], + [(0, 2), (0, 5)], + [(0, 3), (1, 0)], + [(0, 4), (1, 1)], + [(0, 5), (1, 2)], + [(0, 6), (1, 3)], + [(0, 7), (1, 3)], + [(1, 3), (1, 5)], ], ) def test_down(start, end): From 32144433d783eed365727f2f5cac497ce16a045f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 11 Dec 2023 14:18:45 +0000 Subject: [PATCH 047/150] Remove unused clamp --- src/textual/document/_document_navigator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 4bc982eb1b..9887c32e4f 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -206,7 +206,7 @@ def up(self, location: Location, tab_width: int) -> Location: ) target_location = line_index, target_column - return self.clamp_reachable(target_location) + return target_location def down(self, location: Location, tab_width: int) -> Location: """Given a location in the raw document, return the raw document From ca834da5ec1490030a7964f6ab6a30dce5773095 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 11 Dec 2023 15:31:57 +0000 Subject: [PATCH 048/150] Cursor jumping to end of wrapped line --- src/textual/document/_document_navigator.py | 42 ++++++++++++++++----- tests/document/test_document_navigator.py | 24 ++++++++++-- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 9887c32e4f..e026de5050 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -31,7 +31,7 @@ """ import re -from bisect import bisect_left, bisect_right +from bisect import bisect, bisect_left, bisect_right from typing import Any, Sequence from textual._cells import cell_len @@ -42,7 +42,7 @@ class DocumentNavigator: def __init__(self, wrapped_document: WrappedDocument) -> None: - """Create a CursorNavigator. + """Create a DocumentNavigator. Args: wrapped_document: The WrappedDocument to be used when making navigation decisions. @@ -165,7 +165,7 @@ def right(self, location: Location) -> Location: target_column = 0 if is_end_of_line else column + 1 return target_row, target_column - def up(self, location: Location, tab_width: int) -> Location: + def get_location_above(self, location: Location, tab_width: int) -> Location: """Get the location up from the given location in the wrapped document.""" # Moving up from a position on the first visual line moves us to the start. @@ -200,7 +200,6 @@ def up(self, location: Location, tab_width: int) -> Location: # Stay on the same document line, but move backwards. # Since the section above could be shorter, we need to clamp the column # to a valid value. - target_column = self._wrapped_document.get_target_document_column( line_index, current_visual_offset, section_index - 1, tab_width ) @@ -208,7 +207,7 @@ def up(self, location: Location, tab_width: int) -> Location: return target_location - def down(self, location: Location, tab_width: int) -> Location: + def get_location_below(self, location: Location, tab_width: int) -> Location: """Given a location in the raw document, return the raw document location corresponding to moving down in the wrapped representation of the document. @@ -228,7 +227,7 @@ def down(self, location: Location, tab_width: int) -> Location: wrap_offsets = self._wrapped_document.get_offsets(line_index) section_start_columns = [0, *wrap_offsets] - section_index = bisect_right(wrap_offsets, column_index) + section_index = bisect(wrap_offsets, column_index) offset_within_section = column_index - section_start_columns[section_index] wrapped_line = self._wrapped_document.get_sections(line_index) section = wrapped_line[section_index] @@ -241,10 +240,6 @@ def down(self, location: Location, tab_width: int) -> Location: target_column = self._wrapped_document.get_target_document_column( target_row, current_visual_offset, 0, tab_width ) - # TODO - need to decide what to do with the return at the - # bottom of cell_width_to_column_index - we need to be able - # to move to len(final_section) when on final_section. - # However, while on any other section, we must not move beyond. target_location = target_row, target_column else: # Stay on the same document line, but move forwards to @@ -256,6 +251,33 @@ def down(self, location: Location, tab_width: int) -> Location: return target_location + def get_location_end(self, location: Location) -> Location: + """Get the location corresponding to the end of the current section. + + Args: + location: The current location. + + Returns: + The location corresponding to the end of the wrapped line. + """ + line_index, column_offset = location + wrap_offsets = self._wrapped_document.get_offsets(line_index) + if wrap_offsets: + # Get the next wrap offset to the right + next_offset_right = bisect(wrap_offsets, column_offset) + # There's no more wrapping to the right of this location - go to line end. + if next_offset_right == len(wrap_offsets): + return line_index, len(self._document[line_index]) + # We've found a wrap point + return line_index, wrap_offsets[next_offset_right] - 1 + else: + # No wrapping to consider - go to the start/end of the document line. + target_column = len(self._document[line_index]) + return line_index, target_column + + def get_location_home(self, location: Location) -> Location: + """Get the location corresponding to the start of the current section.""" + def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a reachable location in the document. diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 7087930c7d..0dea90b329 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -41,8 +41,8 @@ def make_navigator(text, width): [(1, 5), (1, 1)], ], ) -def test_up(start, end): - assert make_navigator(TEXT, 4).up(start, 4) == end +def test_get_location_above(start, end): + assert make_navigator(TEXT, 4).get_location_above(start, 4) == end @pytest.mark.parametrize( @@ -59,5 +59,21 @@ def test_up(start, end): [(1, 3), (1, 5)], ], ) -def test_down(start, end): - assert make_navigator(TEXT, 4).down(start, 4) == end +def test_get_location_below(start, end): + assert make_navigator(TEXT, 4).get_location_below(start, 4) == end + + +@pytest.mark.parametrize( + "start,end", + [ + [(0, 0), (0, 2)], + [(0, 2), (0, 2)], + [(0, 3), (0, 7)], + [(0, 5), (0, 7)], + [(1, 2), (1, 3)], + [(1, 4), (1, 5)], + [(1, 5), (1, 5)], + ], +) +def test_get_location_end(start, end): + assert make_navigator(TEXT, 4).get_location_end(start) == end From 8a6a97cd3359d6ff55c5b4348497fe740a413d62 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 11 Dec 2023 17:16:05 +0000 Subject: [PATCH 049/150] Moving to the home location in a wrapped document --- src/textual/document/_document_navigator.py | 15 ++++++++++++--- tests/document/test_document_navigator.py | 20 ++++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index e026de5050..a9eb0edb6b 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -103,7 +103,6 @@ def is_first_document_line(self, location: Location) -> bool: return location[0] == 0 def is_first_wrapped_line(self, location: Location) -> bool: - # TODO: Check the less than/equal to thing. # Ensure that the column index of the location is less (or equal to?!?!) than # the first value in the wrap offsets. if not self.is_first_document_line(location): @@ -115,7 +114,7 @@ def is_first_wrapped_line(self, location: Location) -> bool: if not wrap_offsets: return True - if column <= wrap_offsets[0]: + if column < wrap_offsets[0]: return True return False @@ -133,7 +132,7 @@ def is_last_wrapped_line(self, location: Location) -> bool: if not wrap_offsets: return True - if column > wrap_offsets[-1]: + if column >= wrap_offsets[-1]: return True return False @@ -277,6 +276,16 @@ def get_location_end(self, location: Location) -> Location: def get_location_home(self, location: Location) -> Location: """Get the location corresponding to the start of the current section.""" + line_index, column_offset = location + wrap_offsets = self._wrapped_document.get_offsets(line_index) + if wrap_offsets: + next_offset_left = bisect(wrap_offsets, column_offset) + if next_offset_left == 0: + return line_index, 0 + return line_index, wrap_offsets[next_offset_left - 1] + else: + # No wrapping to consider, go to the start of the document line + return line_index, 0 def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 0dea90b329..5a21243cde 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -63,6 +63,24 @@ def test_get_location_below(start, end): assert make_navigator(TEXT, 4).get_location_below(start, 4) == end +@pytest.mark.parametrize( + "start,end", + [ + [(0, 0), (0, 0)], + [(0, 2), (0, 0)], + [(0, 3), (0, 3)], + [(0, 6), (0, 3)], + [(0, 7), (0, 3)], + [(1, 0), (1, 0)], + [(1, 3), (1, 0)], + [(1, 4), (1, 4)], + [(1, 5), (1, 4)], + ], +) +def test_get_location_home(start, end): + assert make_navigator(TEXT, 4).get_location_home(start) == end + + @pytest.mark.parametrize( "start,end", [ @@ -71,8 +89,6 @@ def test_get_location_below(start, end): [(0, 3), (0, 7)], [(0, 5), (0, 7)], [(1, 2), (1, 3)], - [(1, 4), (1, 5)], - [(1, 5), (1, 5)], ], ) def test_get_location_end(start, end): From 099dac3e234afe3f35bcd655caf78960637ecaab Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 12 Dec 2023 11:25:37 +0000 Subject: [PATCH 050/150] Add a TODO comment --- src/textual/document/_document_navigator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index a9eb0edb6b..afef8213c5 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -287,6 +287,11 @@ def get_location_home(self, location: Location) -> Location: # No wrapping to consider, go to the start of the document line return line_index, 0 + # TODO - we need to implement methods for going page up and page down + # perhaps we just need a method: given a location and a y-offset return + # the location corresponding to the y-offset applied to the location in the + # *wrapped* document. + def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a reachable location in the document. From 8d08692b087d55fd9e5e3b527335b1a38d6dc1c7 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 12 Dec 2023 12:46:12 +0000 Subject: [PATCH 051/150] Add wrap reactive to TextArea --- src/textual/widgets/_text_area.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 7f350a276c..4db9b52c69 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -230,6 +230,9 @@ class TextArea(ScrollView, can_focus=True): cursor_blink: Reactive[bool] = reactive(True) """True if the cursor should blink.""" + wrap: Reactive[bool] = reactive(False) + """True if text should soft wrap.""" + _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False) """Indicates where the cursor is in the blink cycle. If it's currently not visible due to blinking, this is False.""" @@ -271,6 +274,7 @@ def __init__( *, language: str | None = None, theme: str | None = None, + wrap: bool = False, name: str | None = None, id: str | None = None, classes: str | None = None, @@ -337,6 +341,8 @@ def __init__( self.theme = theme + self._reactive_wrap = wrap + @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. From 13de4159fe0d73f7ab067cb93fad3aa668518656 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 12 Dec 2023 13:11:47 +0000 Subject: [PATCH 052/150] Remove now-redundant wrap() calls --- src/textual/document/_wrapped_document.py | 13 +++++++------ src/textual/widgets/_text_area.py | 17 +++++++++++++++++ tests/document/test_document_navigator.py | 1 - tests/document/test_wrapped_document.py | 13 ------------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 72cde89a26..de8dfde3fa 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -37,15 +37,16 @@ def __init__( """Maps line indices to the offsets within the line where wrapping breaks should be added.""" - self._offset_to_document_line: list[int] = [] - """Allows us to quickly go from a y-offset within the wrapped document - to the index of the line in the raw document.""" + self.wrap(width) - def wrap(self) -> None: - """Wrap and cache all lines in the document.""" + def wrap(self, width: int) -> None: + """Wrap and cache all lines in the document. + + Args: + width: The width to wrap at. + """ new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append - width = self._width for line in self.document.lines: wrap_offsets = divide_line(line, width) if width else [] diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 4db9b52c69..dc4b091f07 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -27,6 +27,7 @@ SyntaxAwareDocument, SyntaxAwareDocumentError, ) +from textual.document._wrapped_document import WrappedDocument from textual.expand_tabs import expand_tabs_inline if TYPE_CHECKING: @@ -332,6 +333,9 @@ def __init__( self.document: DocumentBase = Document(text) """The document this widget is currently editing.""" + self.wrapped_document = WrappedDocument(self.document) + """The wrapped view of the document.""" + self._theme: TextAreaTheme | None = None """The `TextAreaTheme` corresponding to the set theme name. When the `theme` reactive is set as a string, the watcher will update this attribute to the @@ -656,6 +660,7 @@ def _set_document(self, text: str, language: str | None) -> None: document = Document(text) self.document = document + self.wrapped_document = WrappedDocument(document) self._build_highlight_map() @property @@ -686,6 +691,17 @@ def load_text(self, text: str) -> None: self.move_cursor((0, 0)) self._refresh_size() + def _on_resize(self) -> None: + self._rewrap() + + def _watch_wrap(self) -> None: + self._rewrap() + + def _rewrap(self) -> None: + width, _ = self.size + self.wrapped_document.wrap(width) + log.debug(f"re-wrapping at width {width!r}") + def load_document(self, document: DocumentBase) -> None: """Load a document into the TextArea. @@ -695,6 +711,7 @@ def load_document(self, document: DocumentBase) -> None: self.document = document self.move_cursor((0, 0)) self._refresh_size() + self._rewrap() @property def is_syntax_aware(self) -> bool: diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 5a21243cde..472094d40c 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -20,7 +20,6 @@ def make_navigator(text, width): document = Document(text) wrapped_document = WrappedDocument(document, width) - wrapped_document.wrap() navigator = DocumentNavigator(wrapped_document) return navigator diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index bb6103d07c..7184870f4e 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -10,7 +10,6 @@ def test_wrap(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() assert wrapped_document.lines == [ ["123 ", "4567"], @@ -23,7 +22,6 @@ def test_wrap(): def test_wrap_empty_document(): document = Document("") wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() assert wrapped_document.lines == [[""]] @@ -31,7 +29,6 @@ def test_wrap_empty_document(): def test_wrap_width_zero_no_wrapping(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=0) - wrapped_document.wrap() assert wrapped_document.lines == [ ["123 4567"], @@ -45,7 +42,6 @@ def test_refresh_range(): """The post-edit content is not wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() start_location = (1, 0) old_end_location = (3, 0) @@ -65,7 +61,6 @@ def test_refresh_range_new_text_wrapped(): """The post-edit content itself must be wrapped.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() start_location = (1, 0) old_end_location = (3, 0) @@ -90,7 +85,6 @@ def test_refresh_range_wrapping_at_previously_unavailable_range(): """When we insert new content at the end of the document, ensure it wraps correctly.""" document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) @@ -108,7 +102,6 @@ def test_refresh_range_wrapping_at_previously_unavailable_range(): def test_refresh_range_wrapping_disabled_previously_unavailable_range(): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=0) # wrapping disabled - wrapped_document.wrap() edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) @@ -140,7 +133,6 @@ def test_refresh_range_wrapping_disabled_previously_unavailable_range(): def test_offset_to_location_wrapping_enabled(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() assert wrapped_document.offset_to_location(offset, 2) == location @@ -160,7 +152,6 @@ def test_offset_to_location_wrapping_enabled(offset, location): def test_offset_to_location_wrapping_disabled(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=0) - wrapped_document.wrap() assert wrapped_document.offset_to_location(offset, 4) == location @@ -169,7 +160,6 @@ def test_offset_to_location_wrapping_disabled(offset, location): def test_offset_to_location_invalid_offset_raises_exception(offset): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() with pytest.raises(ValueError): wrapped_document.offset_to_location(offset, 10) @@ -186,7 +176,6 @@ def test_offset_to_location_invalid_offset_raises_exception(offset): def test_get_offsets(line_index, offsets): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() assert wrapped_document.get_offsets(line_index) == offsets @@ -194,7 +183,6 @@ def test_get_offsets(line_index, offsets): def test_get_offsets_no_wrapping(): document = Document("abc") wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() assert wrapped_document.get_offsets(0) == [] @@ -203,7 +191,6 @@ def test_get_offsets_no_wrapping(): def test_get_offsets_invalid_line_index(line_index): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - wrapped_document.wrap() with pytest.raises(ValueError): wrapped_document.get_offsets(line_index) From 354b7ce0b23f132215782c6768ddc3eb26a03fbb Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 12 Dec 2023 16:11:52 +0000 Subject: [PATCH 053/150] Build up the offset map in wrapped document to allow quick lookups of offsets to lines --- src/textual/document/_wrapped_document.py | 54 +++++++++++++++++------ src/textual/widgets/_text_area.py | 18 ++++++-- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index de8dfde3fa..76e8324981 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -19,39 +19,49 @@ class WrappedDocument: def __init__( self, document: DocumentBase, - width: int = 0, ) -> None: """Construct a WrappedDocument. + By default, a WrappedDocument is wrapped with width=0 (no wrapping). + To wrap the document, use the wrap() method. + Args: document: The document to wrap. - width: The cell-width to wrap at. 0 for no wrapping. """ self.document = document """The document wrapping is performed on.""" - self._width = width - """The maximum cell-width per line.""" - self._wrap_offsets: list[list[int]] = [] """Maps line indices to the offsets within the line where wrapping breaks should be added.""" - self.wrap(width) + self._offset_map: dict[int, tuple[int, int]] = {} + """Maps y_offsets (from the top of the document) to (line_index, vertical offset within + that wrapped line) tuples.""" + + self.wrap(0) def wrap(self, width: int) -> None: """Wrap and cache all lines in the document. Args: - width: The width to wrap at. + width: The width to wrap at. 0 for no wrapping. """ new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append + offset_map = {} + current_offset = 0 - for line in self.document.lines: + for line_index, line in enumerate(self.document.lines): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) + # TODO - build up the mapping of current_offset -> line_index + + for _ in range(len(wrap_offsets) + 1): + offset_map[current_offset] = line_index + current_offset += 1 + self._offset_map = offset_map self._wrap_offsets = new_wrap_offsets @property @@ -61,14 +71,23 @@ def lines(self) -> list[list[str]]: Each index in the returned list represents a line index in the raw document. The list[str] at each index is the content of the raw document line split into multiple lines via wrapping. + + Returns: + A list of lines from the wrapped version of the document. """ wrapped_lines = [] append = wrapped_lines.append for line_index, line in enumerate(self.document.lines): divided = Text(line).divide(self._wrap_offsets[line_index]) append([section.plain for section in divided]) + return wrapped_lines + @property + def height(self) -> int: + """The height of the wrapped document.""" + return sum(len(offsets) + 1 for offsets in self._wrap_offsets) + def refresh_range( self, start: Location, @@ -95,6 +114,9 @@ def refresh_range( new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append width = self._width + + # TODO- update the offset map here too + for line_index, line in enumerate(new_lines, start_row): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) @@ -105,7 +127,7 @@ def refresh_range( def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, - return the corresponding line index. + return the corresponding location in the document. Args: offset: The y-offset within the document. @@ -120,7 +142,7 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """ x, y = offset if x < 0 or y < 0: - raise ValueError("Offset must be positive.") + raise ValueError("Offset must be non-negative.") if not self._width: # No wrapping, so we directly map offset to location and clamp. @@ -130,19 +152,23 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Find the line corresponding to the given y offset in the wrapped document. current_offset = 0 + get_target_document_column = self.get_target_document_column for line_index, line_offsets in enumerate(self._wrap_offsets): next_offset = current_offset + len(line_offsets) + 1 if next_offset > y: # We've found the vertical offset. - return line_index, self.get_target_document_column( + location = line_index, get_target_document_column( line_index, x, y - current_offset, tab_width ) + break current_offset = next_offset + else: + location = len(self._wrap_offsets) - 1, get_target_document_column( + -1, x, -1, tab_width + ) # Offset doesn't match any line => land on bottom wrapped line - return len(self._wrap_offsets) - 1, self.get_target_document_column( - -1, x, -1, tab_width - ) + return location def get_target_document_column( self, diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index dc4b091f07..0245b03b05 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -774,17 +774,29 @@ def render_line(self, widget_y: int) -> Strip: Returns: A rendered line. """ + document = self.document + wrapped_document = self.wrapped_document scroll_x, scroll_y = self.scroll_offset # Account for how much the TextArea is scrolled. - line_index = widget_y + scroll_y + y_offset = widget_y + scroll_y + offset = Offset(0, y_offset) - # Render the lines beyond the valid line numbers - out_of_bounds = line_index >= document.line_count + # If we're beyond the height of the document, render blank lines + out_of_bounds = y_offset >= wrapped_document.height if out_of_bounds: return Strip.blank(self.size.width) + # Get the line corresponding to this offset + line_index, _ = wrapped_document.offset_to_location( + offset, tab_width=self.indent_width + ) + + # TODO: We'll need to do an offset -> wrapped_line cache to avoid doing a lot of work here. + # Use document.edit_count in the cache key. + wrap_offsets = wrapped_document.get_offsets(line_index) + theme = self._theme # Get the line from the Document. From fab9e020717966787faa4b0a6bd2d72c03631520 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 13 Dec 2023 10:27:38 +0000 Subject: [PATCH 054/150] Computing line -> offset when wrapping occurs --- src/textual/document/_wrapped_document.py | 17 ++++++++++++----- src/textual/widgets/_text_area.py | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 76e8324981..445cfa6b3e 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -39,7 +39,11 @@ def __init__( """Maps y_offsets (from the top of the document) to (line_index, vertical offset within that wrapped line) tuples.""" - self.wrap(0) + self._width: int = 0 + """The width the document is currently wrapped at. This will correspond with + the value last passed into the `wrap` method.""" + + self.wrap(self._width) def wrap(self, width: int) -> None: """Wrap and cache all lines in the document. @@ -47,6 +51,7 @@ def wrap(self, width: int) -> None: Args: width: The width to wrap at. 0 for no wrapping. """ + self._width = width new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append offset_map = {} @@ -55,8 +60,6 @@ def wrap(self, width: int) -> None: for line_index, line in enumerate(self.document.lines): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) - # TODO - build up the mapping of current_offset -> line_index - for _ in range(len(wrap_offsets) + 1): offset_map[current_offset] = line_index current_offset += 1 @@ -114,15 +117,19 @@ def refresh_range( new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append width = self._width - - # TODO- update the offset map here too + offset_map = {} + current_offset = 0 for line_index, line in enumerate(new_lines, start_row): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) + for _ in range(len(wrap_offsets) + 1): + offset_map[current_offset] = line_index + current_offset += 1 # Replace the range start -> old with the new wrapped lines old_end_row, _ = old_end + self._offset_map = offset_map self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets def offset_to_location(self, offset: Offset, tab_width: int) -> Location: diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 0245b03b05..baaa199022 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -793,8 +793,8 @@ def render_line(self, widget_y: int) -> Strip: offset, tab_width=self.indent_width ) - # TODO: We'll need to do an offset -> wrapped_line cache to avoid doing a lot of work here. - # Use document.edit_count in the cache key. + # TODO: We'll need to do an offset -> wrapped_line cache to avoid doing a lot + # of work here. Use document.edit_count in the cache key. wrap_offsets = wrapped_document.get_offsets(line_index) theme = self._theme From 9ca11d2c37df7c1ad899376990c65830677ce44f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 13 Dec 2023 13:32:44 +0000 Subject: [PATCH 055/150] Integrating wrapping with no width --- src/textual/document/_wrapped_document.py | 33 +++++++++++------------ src/textual/widgets/_text_area.py | 28 +++++++++++-------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 445cfa6b3e..161d48f94a 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -60,8 +60,8 @@ def wrap(self, width: int) -> None: for line_index, line in enumerate(self.document.lines): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) - for _ in range(len(wrap_offsets) + 1): - offset_map[current_offset] = line_index + for section_y_offset in range(len(wrap_offsets) + 1): + offset_map[current_offset] = (line_index, section_y_offset) current_offset += 1 self._offset_map = offset_map @@ -123,8 +123,8 @@ def refresh_range( for line_index, line in enumerate(new_lines, start_row): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) - for _ in range(len(wrap_offsets) + 1): - offset_map[current_offset] = line_index + for section_y_offset in range(len(wrap_offsets) + 1): + offset_map[current_offset] = (line_index, section_y_offset) current_offset += 1 # Replace the range start -> old with the new wrapped lines @@ -153,22 +153,21 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: if not self._width: # No wrapping, so we directly map offset to location and clamp. - row_index = min(y, len(self._wrap_offsets) - 1) - column_index = min(x, len(self.document.get_line(row_index))) - return row_index, column_index + line_index = min(y, len(self._wrap_offsets) - 1) + column_index = min(x, len(self.document.get_line(line_index))) + return line_index, column_index # Find the line corresponding to the given y offset in the wrapped document. - current_offset = 0 get_target_document_column = self.get_target_document_column - for line_index, line_offsets in enumerate(self._wrap_offsets): - next_offset = current_offset + len(line_offsets) + 1 - if next_offset > y: - # We've found the vertical offset. - location = line_index, get_target_document_column( - line_index, x, y - current_offset, tab_width - ) - break - current_offset = next_offset + offset_data = self._offset_map.get(y) + if offset_data is not None: + line_index, section_y = offset_data + location = line_index, get_target_document_column( + line_index, + x, + section_y, + tab_width, + ) else: location = len(self._wrap_offsets) - 1, get_target_document_column( -1, x, -1, tab_width diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index baaa199022..9199436a0a 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -698,9 +698,10 @@ def _watch_wrap(self) -> None: self._rewrap() def _rewrap(self) -> None: - width, _ = self.size - self.wrapped_document.wrap(width) - log.debug(f"re-wrapping at width {width!r}") + if self.wrap: + width, _ = self.size + self.wrapped_document.wrap(width) + log.debug(f"re-wrapping at width {width!r}") def load_document(self, document: DocumentBase) -> None: """Load a document into the TextArea. @@ -761,6 +762,9 @@ def _yield_character_locations_reverse( def _refresh_size(self) -> None: """Update the virtual size of the TextArea.""" + if self.wrap: + return + width, height = self.document.get_size(self.indent_width) # +1 width to make space for the cursor resting at the end of the line self.virtual_size = Size(width + self.gutter_width + 1, height) @@ -789,13 +793,7 @@ def render_line(self, widget_y: int) -> Strip: return Strip.blank(self.size.width) # Get the line corresponding to this offset - line_index, _ = wrapped_document.offset_to_location( - offset, tab_width=self.indent_width - ) - - # TODO: We'll need to do an offset -> wrapped_line cache to avoid doing a lot - # of work here. Use document.edit_count in the cache key. - wrap_offsets = wrapped_document.get_offsets(line_index) + line_index, section_offset = wrapped_document._offset_map.get(y_offset) theme = self._theme @@ -922,6 +920,15 @@ def render_line(self, widget_y: int) -> Strip: else: gutter = Text("", end="") + # TODO: Lets not apply the division each time through render_line. + # We should cache sections with the edit counts. + wrap_offsets = wrapped_document.get_offsets(line_index) + + # Join and return the gutter and the visible portion of this line + if wrap_offsets: + sections = line.divide(wrap_offsets) # TODO cache result with edit count + line = sections[section_offset] + # Render the gutter and the text of this line console = self.app.console gutter_segments = console.render(gutter) @@ -946,7 +953,6 @@ def render_line(self, widget_y: int) -> Strip: expanded_length, theme.base_style if theme else None ) - # Join and return the gutter and the visible portion of this line strip = Strip.join([gutter_strip, text_strip]).simplify() return strip.apply_style( From f7877cee7afa2b8234be1e43e2c4098090c62322 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 15 Dec 2023 17:44:55 +0000 Subject: [PATCH 056/150] Approaching range wrapping again --- src/textual/document/_wrapped_document.py | 64 ++++++++++++++++------- src/textual/widgets/_text_area.py | 50 ++++++++++++------ tests/document/test_wrapped_document.py | 8 +-- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 161d48f94a..cc3e0d4473 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -7,6 +7,9 @@ """ from __future__ import annotations +from collections import defaultdict +from typing import NewType + from rich._wrap import divide_line from rich.text import Text @@ -14,6 +17,10 @@ from textual.document._document import DocumentBase, Location from textual.geometry import Offset +VerticalOffset = NewType("VerticalOffset", int) +LineIndex = NewType("LineIndex", int) +SectionOffset = NewType("SectionOffset", int) + class WrappedDocument: def __init__( @@ -35,9 +42,17 @@ def __init__( """Maps line indices to the offsets within the line where wrapping breaks should be added.""" - self._offset_map: dict[int, tuple[int, int]] = {} - """Maps y_offsets (from the top of the document) to (line_index, vertical offset within - that wrapped line) tuples.""" + self._offset_to_line_info: dict[ + VerticalOffset, tuple[LineIndex, SectionOffset] + ] = {} + """Maps y_offsets (from the top of the document) to line_index and the offset + of the section within the line.""" + + self._line_index_to_offsets: dict[list[VerticalOffset]] = defaultdict(list) + """Maps line indices to all the vertical offsets which correspond to that line.""" + + # self._offset_to_section_offset: dict[int, int] = {} + # """Maps y_offsets to the offsets of the section within the line.""" self._width: int = 0 """The width the document is currently wrapped at. This will correspond with @@ -52,19 +67,27 @@ def wrap(self, width: int) -> None: width: The width to wrap at. 0 for no wrapping. """ self._width = width + + # We're starting wrapping from scratch, so use fresh new_wrap_offsets = [] + offset_to_line_info = {} + line_index_to_offsets: dict[LineIndex, list[VerticalOffset]] = defaultdict(list) + append_wrap_offset = new_wrap_offsets.append - offset_map = {} current_offset = 0 for line_index, line in enumerate(self.document.lines): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) for section_y_offset in range(len(wrap_offsets) + 1): - offset_map[current_offset] = (line_index, section_y_offset) + offset_to_line_info[current_offset] = (line_index, section_y_offset) + line_index_to_offsets[LineIndex(line_index)].append( + VerticalOffset(current_offset) + ) current_offset += 1 - self._offset_map = offset_map + self._offset_to_line_info = offset_to_line_info + self._line_index_to_offsets = line_index_to_offsets self._wrap_offsets = new_wrap_offsets @property @@ -91,7 +114,7 @@ def height(self) -> int: """The height of the wrapped document.""" return sum(len(offsets) + 1 for offsets in self._wrap_offsets) - def refresh_range( + def wrap_range( self, start: Location, old_end: Location, @@ -108,29 +131,34 @@ def refresh_range( """ # Get all the text on the lines between start and end in document space - start_row, _ = start - end_row, _ = new_end + start_line_index, _ = start + old_end_line_index, _ = old_end + new_end_line_index, _ = new_end + + # TODO on Monday: + # - we need to clear the affected ranges in the 3 data structures we're maintaining + # - first get all the offsets corresponding to the line range. + # - now clear all those offsets and line indices in the data structures. # +1 since we go to the start of the next row, and +1 for inclusive. - new_lines = self.document.lines[start_row : end_row + 2] + new_lines = self.document.lines[start_line_index : new_end_line_index + 2] new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append width = self._width - offset_map = {} - current_offset = 0 - for line_index, line in enumerate(new_lines, start_row): + # Add the new offsets between start and new end (the new post-edit offsets) + for line_index, line in enumerate(new_lines, start_line_index): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) - for section_y_offset in range(len(wrap_offsets) + 1): - offset_map[current_offset] = (line_index, section_y_offset) + for section_offset in range(len(wrap_offsets) + 1): + self._offset_to_line_index[current_offset] = line_index + self._offset_to_section_offset[current_offset] = section_offset current_offset += 1 # Replace the range start -> old with the new wrapped lines old_end_row, _ = old_end - self._offset_map = offset_map - self._wrap_offsets[start_row:old_end_row] = new_wrap_offsets + self._wrap_offsets[start_line_index:old_end_row] = new_wrap_offsets def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, @@ -159,7 +187,7 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Find the line corresponding to the given y offset in the wrapped document. get_target_document_column = self.get_target_document_column - offset_data = self._offset_map.get(y) + offset_data = self._offset_to_line_index.get(y) if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 9199436a0a..1d2afb0ee6 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -700,8 +700,9 @@ def _watch_wrap(self) -> None: def _rewrap(self) -> None: if self.wrap: width, _ = self.size - self.wrapped_document.wrap(width) - log.debug(f"re-wrapping at width {width!r}") + available_text_width = width - self.gutter_width - 1 + self.wrapped_document.wrap(available_text_width) + log.debug(f"re-wrapping at width {available_text_width!r}") def load_document(self, document: DocumentBase) -> None: """Load a document into the TextArea. @@ -779,13 +780,15 @@ def render_line(self, widget_y: int) -> Strip: A rendered line. """ + print("render_line") + document = self.document wrapped_document = self.wrapped_document scroll_x, scroll_y = self.scroll_offset # Account for how much the TextArea is scrolled. y_offset = widget_y + scroll_y - offset = Offset(0, y_offset) + print(f"yoffset = {y_offset}") # If we're beyond the height of the document, render blank lines out_of_bounds = y_offset >= wrapped_document.height @@ -793,7 +796,11 @@ def render_line(self, widget_y: int) -> Strip: return Strip.blank(self.size.width) # Get the line corresponding to this offset - line_index, section_offset = wrapped_document._offset_map.get(y_offset) + # print(wrapped_document.height) + # print(wrapped_document._offset_to_line_index._forward) + + line_index = wrapped_document._offset_to_line_index.get(y_offset) + section_offset = wrapped_document._offset_to_section_offset.get(y_offset) theme = self._theme @@ -804,8 +811,8 @@ def render_line(self, widget_y: int) -> Strip: line_character_count = len(line) line.tab_size = self.indent_width virtual_width, virtual_height = self.virtual_size - expanded_length = max(virtual_width, self.size.width) - line.set_length(expanded_length) + expanded_width = max(virtual_width, self.size.width) + # line.set_length(expanded_length) selection = self.selection start, end = selection @@ -912,8 +919,9 @@ def render_line(self, widget_y: int) -> Strip: gutter_style = theme.gutter_style if theme else None gutter_width_no_margin = gutter_width - 2 + gutter_content = str(line_index + 1) if section_offset == 0 else "" gutter = Text( - f"{line_index + 1:>{gutter_width_no_margin}} ", + f"{gutter_content:>{gutter_width_no_margin}} ", style=gutter_style or "", end="", ) @@ -932,25 +940,28 @@ def render_line(self, widget_y: int) -> Strip: # Render the gutter and the text of this line console = self.app.console gutter_segments = console.render(gutter) + width, _ = self.size + section_width = width - self.gutter_width + line.set_length(section_width) text_segments = console.render( line, - console.options.update_width(expanded_length), + console.options.update_width(section_width), ) # Crop the line to show only the visible part (some may be scrolled out of view) gutter_strip = Strip(gutter_segments, cell_length=gutter_width) - text_strip = Strip(text_segments).crop( - scroll_x, scroll_x + virtual_width - gutter_width - ) + text_strip = Strip(text_segments) + if not self.wrap: + text_strip = text_strip.crop( + scroll_x, scroll_x + virtual_width - gutter_width + ) # Stylize the line the cursor is currently on. if cursor_row == line_index: - text_strip = text_strip.extend_cell_length( - expanded_length, cursor_line_style - ) + text_strip = text_strip.extend_cell_length(section_width, cursor_line_style) else: text_strip = text_strip.extend_cell_length( - expanded_length, theme.base_style if theme else None + section_width, theme.base_style if theme else None ) strip = Strip.join([gutter_strip, text_strip]).simplify() @@ -994,7 +1005,7 @@ def get_text_range(self, start: Location, end: Location) -> str: start, end = sorted((start, end)) return self.document.get_text_range(start, end) - def edit(self, edit: Edit) -> Any: + def edit(self, edit: Edit) -> EditResult: """Perform an Edit. Args: @@ -1004,10 +1015,17 @@ def edit(self, edit: Edit) -> Any: Data relating to the edit that may be useful. The data returned may be different depending on the edit performed. """ + print("doing edit") result = edit.do(self) self._refresh_size() edit.after(self) self._build_highlight_map() + print(f"edit.from_location = {edit.from_location!r}") + print(f"edit.to_location = {edit.to_location!r}") + print(f"result.end_location = {result.end_location!r}") + self.wrapped_document.wrap_range( + edit.from_location, edit.to_location, result.end_location + ) self.post_message(self.Changed(self)) return result diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 7184870f4e..1a62a5aa85 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -49,7 +49,7 @@ def test_refresh_range(): edit_result = document.replace_range(start_location, old_end_location, "123") # Inform the wrapped document about the range impacted by the edit - wrapped_document.refresh_range( + wrapped_document.wrap_range( start_location, old_end_location, edit_result.end_location ) @@ -70,7 +70,7 @@ def test_refresh_range_new_text_wrapped(): ) # Inform the wrapped document about the range impacted by the edit - wrapped_document.refresh_range( + wrapped_document.wrap_range( start_location, old_end_location, edit_result.end_location ) @@ -87,7 +87,7 @@ def test_refresh_range_wrapping_at_previously_unavailable_range(): wrapped_document = WrappedDocument(document, width=4) edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") - wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) + wrapped_document.wrap_range((3, 0), (3, 0), edit_result.end_location) assert wrapped_document.lines == [ ["123 ", "4567"], @@ -104,7 +104,7 @@ def test_refresh_range_wrapping_disabled_previously_unavailable_range(): wrapped_document = WrappedDocument(document, width=0) # wrapping disabled edit_result = document.replace_range((3, 0), (3, 0), "012 3456\n78 90123\n45") - wrapped_document.refresh_range((3, 0), (3, 0), edit_result.end_location) + wrapped_document.wrap_range((3, 0), (3, 0), edit_result.end_location) assert wrapped_document.lines == [ ["123 4567"], From 30ad4c32c22a067d705fd1af4b11563cfa49ac13 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 18 Dec 2023 12:02:28 +0000 Subject: [PATCH 057/150] Improving wrapped document internal data structures. --- src/textual/document/_wrapped_document.py | 36 +++++++++++++---------- src/textual/widgets/_text_area.py | 7 +---- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index cc3e0d4473..d859ec8714 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -8,7 +8,6 @@ from __future__ import annotations from collections import defaultdict -from typing import NewType from rich._wrap import divide_line from rich.text import Text @@ -17,9 +16,9 @@ from textual.document._document import DocumentBase, Location from textual.geometry import Offset -VerticalOffset = NewType("VerticalOffset", int) -LineIndex = NewType("LineIndex", int) -SectionOffset = NewType("SectionOffset", int) +VerticalOffset = int +LineIndex = int +SectionOffset = int class WrappedDocument: @@ -48,7 +47,9 @@ def __init__( """Maps y_offsets (from the top of the document) to line_index and the offset of the section within the line.""" - self._line_index_to_offsets: dict[list[VerticalOffset]] = defaultdict(list) + self._line_index_to_offsets: dict[ + LineIndex, list[VerticalOffset] + ] = defaultdict(list) """Maps line indices to all the vertical offsets which correspond to that line.""" # self._offset_to_section_offset: dict[int, int] = {} @@ -81,9 +82,7 @@ def wrap(self, width: int) -> None: append_wrap_offset(wrap_offsets) for section_y_offset in range(len(wrap_offsets) + 1): offset_to_line_info[current_offset] = (line_index, section_y_offset) - line_index_to_offsets[LineIndex(line_index)].append( - VerticalOffset(current_offset) - ) + line_index_to_offsets[line_index].append(current_offset) current_offset += 1 self._offset_to_line_info = offset_to_line_info @@ -135,14 +134,19 @@ def wrap_range( old_end_line_index, _ = old_end new_end_line_index, _ = new_end - # TODO on Monday: - # - we need to clear the affected ranges in the 3 data structures we're maintaining - # - first get all the offsets corresponding to the line range. - # - now clear all those offsets and line indices in the data structures. + # Can we only rewrap a section of the line starting at the wrap point + # to the left of the edit? There may be something we can do here. However, + # an edit can alter wrap points before it. - # +1 since we go to the start of the next row, and +1 for inclusive. - new_lines = self.document.lines[start_line_index : new_end_line_index + 2] + # Clearing old cached data + current_offset = self._line_index_to_offsets.get(start_line_index)[0] + for line_index in range(start_line_index, old_end_line_index + 1): + offsets = self._line_index_to_offsets.get(line_index) + del self._line_index_to_offsets[line_index] + for offset in offsets: + del self._offset_to_line_info[offset] + new_lines = self.document.lines[start_line_index : new_end_line_index + 1] new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append width = self._width @@ -152,8 +156,8 @@ def wrap_range( wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) for section_offset in range(len(wrap_offsets) + 1): - self._offset_to_line_index[current_offset] = line_index - self._offset_to_section_offset[current_offset] = section_offset + self._line_index_to_offsets[line_index] = wrap_offsets + self._offset_to_line_info[current_offset] = (line_index, section_offset) current_offset += 1 # Replace the range start -> old with the new wrapped lines diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 1d2afb0ee6..6695412b00 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -788,7 +788,6 @@ def render_line(self, widget_y: int) -> Strip: # Account for how much the TextArea is scrolled. y_offset = widget_y + scroll_y - print(f"yoffset = {y_offset}") # If we're beyond the height of the document, render blank lines out_of_bounds = y_offset >= wrapped_document.height @@ -796,11 +795,7 @@ def render_line(self, widget_y: int) -> Strip: return Strip.blank(self.size.width) # Get the line corresponding to this offset - # print(wrapped_document.height) - # print(wrapped_document._offset_to_line_index._forward) - - line_index = wrapped_document._offset_to_line_index.get(y_offset) - section_offset = wrapped_document._offset_to_section_offset.get(y_offset) + line_index, section_offset = wrapped_document._offset_to_line_info.get(y_offset) theme = self._theme From efb04ccdec6a7fc5e2d6b5c1745c056c88b70e61 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 18 Dec 2023 16:03:40 +0000 Subject: [PATCH 058/150] Building offset mapping correctly on edit --- src/textual/document/_wrapped_document.py | 23 ++++++++++++++++++----- src/textual/widgets/_text_area.py | 14 +++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index d859ec8714..3aa11249b5 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -88,6 +88,8 @@ def wrap(self, width: int) -> None: self._offset_to_line_info = offset_to_line_info self._line_index_to_offsets = line_index_to_offsets self._wrap_offsets = new_wrap_offsets + print(f"BUILT offset_to_line_info = \n{offset_to_line_info!r}") + print(f"BUILT line_index_to_offsets = \n{line_index_to_offsets!r}") @property def lines(self) -> list[list[str]]: @@ -140,13 +142,22 @@ def wrap_range( # Clearing old cached data current_offset = self._line_index_to_offsets.get(start_line_index)[0] + + print("Wrap--") + print( + f"ranges = start = {start_line_index!r}, old_end = {old_end!r}, new_end = {new_end!r}" + ) for line_index in range(start_line_index, old_end_line_index + 1): offsets = self._line_index_to_offsets.get(line_index) - del self._line_index_to_offsets[line_index] + print(f"offsets for this range (will be deleted) = {offsets!r}") + print(f"offset to line info contains = {self._offset_to_line_info!r}") for offset in offsets: del self._offset_to_line_info[offset] + del self._line_index_to_offsets[line_index] new_lines = self.document.lines[start_line_index : new_end_line_index + 1] + print(f"new lines for range = {new_lines!r}") + new_wrap_offsets = [] append_wrap_offset = new_wrap_offsets.append width = self._width @@ -156,13 +167,15 @@ def wrap_range( wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) for section_offset in range(len(wrap_offsets) + 1): - self._line_index_to_offsets[line_index] = wrap_offsets + self._line_index_to_offsets[line_index].append(current_offset) self._offset_to_line_info[current_offset] = (line_index, section_offset) current_offset += 1 # Replace the range start -> old with the new wrapped lines - old_end_row, _ = old_end - self._wrap_offsets[start_line_index:old_end_row] = new_wrap_offsets + print(f"after update, new wrap offsets = \n{new_wrap_offsets!r}") + print(f"length of offsets before = {len(self._wrap_offsets)}") + self._wrap_offsets[start_line_index : old_end_line_index + 1] = new_wrap_offsets + print(f"length of offsets after = {len(self._wrap_offsets)}") def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, @@ -191,7 +204,7 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Find the line corresponding to the given y offset in the wrapped document. get_target_document_column = self.get_target_document_column - offset_data = self._offset_to_line_index.get(y) + offset_data = self._offset_to_line_info.get(y) if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 6695412b00..bb57de5590 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -779,9 +779,6 @@ def render_line(self, widget_y: int) -> Strip: Returns: A rendered line. """ - - print("render_line") - document = self.document wrapped_document = self.wrapped_document scroll_x, scroll_y = self.scroll_offset @@ -791,11 +788,18 @@ def render_line(self, widget_y: int) -> Strip: # If we're beyond the height of the document, render blank lines out_of_bounds = y_offset >= wrapped_document.height + blank_line = Strip.blank(self.size.width) + if out_of_bounds: - return Strip.blank(self.size.width) + return blank_line + print(f"current y_offset = {y_offset!r}") # Get the line corresponding to this offset - line_index, section_offset = wrapped_document._offset_to_line_info.get(y_offset) + line_info = wrapped_document._offset_to_line_info.get(y_offset) + if line_info is None: + return blank_line + + line_index, section_offset = line_info theme = self._theme From 1b3e8d00a0a6ecc83776310ea17ca7da13cc924c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 20 Dec 2023 18:18:14 +0000 Subject: [PATCH 059/150] Fixing some logic, but still crashes in some situations --- src/textual/document/_wrapped_document.py | 96 ++++++++++++++++------- src/textual/widgets/_text_area.py | 9 ++- 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 3aa11249b5..d55b17d069 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -7,8 +7,6 @@ """ from __future__ import annotations -from collections import defaultdict - from rich._wrap import divide_line from rich.text import Text @@ -41,15 +39,11 @@ def __init__( """Maps line indices to the offsets within the line where wrapping breaks should be added.""" - self._offset_to_line_info: dict[ - VerticalOffset, tuple[LineIndex, SectionOffset] - ] = {} + self._offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] """Maps y_offsets (from the top of the document) to line_index and the offset of the section within the line.""" - self._line_index_to_offsets: dict[ - LineIndex, list[VerticalOffset] - ] = defaultdict(list) + self._line_index_to_offsets: list[list[VerticalOffset]] = [] """Maps line indices to all the vertical offsets which correspond to that line.""" # self._offset_to_section_offset: dict[int, int] = {} @@ -71,8 +65,8 @@ def wrap(self, width: int) -> None: # We're starting wrapping from scratch, so use fresh new_wrap_offsets = [] - offset_to_line_info = {} - line_index_to_offsets: dict[LineIndex, list[VerticalOffset]] = defaultdict(list) + offset_to_line_info = [] + line_index_to_offsets: list[list[VerticalOffset]] = [] append_wrap_offset = new_wrap_offsets.append current_offset = 0 @@ -80,8 +74,9 @@ def wrap(self, width: int) -> None: for line_index, line in enumerate(self.document.lines): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) + line_index_to_offsets.append([]) for section_y_offset in range(len(wrap_offsets) + 1): - offset_to_line_info[current_offset] = (line_index, section_y_offset) + offset_to_line_info.append((line_index, section_y_offset)) line_index_to_offsets[line_index].append(current_offset) current_offset += 1 @@ -132,6 +127,10 @@ def wrap_range( """ # Get all the text on the lines between start and end in document space + + start, old_end = sorted((start, old_end)) + start, new_end = sorted((start, new_end)) + start_line_index, _ = start old_end_line_index, _ = old_end new_end_line_index, _ = new_end @@ -141,41 +140,82 @@ def wrap_range( # an edit can alter wrap points before it. # Clearing old cached data - current_offset = self._line_index_to_offsets.get(start_line_index)[0] + start_y_offset = self._line_index_to_offsets[start_line_index][0] + old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] + old_height = old_end_y_offset - start_y_offset print("Wrap--") print( f"ranges = start = {start_line_index!r}, old_end = {old_end!r}, new_end = {new_end!r}" ) - for line_index in range(start_line_index, old_end_line_index + 1): - offsets = self._line_index_to_offsets.get(line_index) - print(f"offsets for this range (will be deleted) = {offsets!r}") - print(f"offset to line info contains = {self._offset_to_line_info!r}") - for offset in offsets: - del self._offset_to_line_info[offset] - del self._line_index_to_offsets[line_index] + # TODO - we're deleting two lines here and sometimes only replacing them + # with one thing. This seems to happen when we delete. + + # Replace the old offsets with the new offsets + # The old offsets are those between start -> old_end inclusive + # The new offsets are those between start -> new_end inclusive + # We'll need to retrieve the offsets from the locations new_lines = self.document.lines[start_line_index : new_end_line_index + 1] - print(f"new lines for range = {new_lines!r}") - new_wrap_offsets = [] + new_wrap_offsets: list[int] = [] + new_line_index_to_offsets: list[list[VerticalOffset]] = [] + new_offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] append_wrap_offset = new_wrap_offsets.append width = self._width # Add the new offsets between start and new end (the new post-edit offsets) + current_y_offset = start_y_offset + + # TODO - I think we cannot just iterate over new_lines here + # If we replace some text, the newlines could be a single line + # But the offsets we need to update for line_index, line in enumerate(new_lines, start_line_index): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) + + # Collect up the new y offsets for this document line + y_offsets_for_line: list[int] = [] for section_offset in range(len(wrap_offsets) + 1): - self._line_index_to_offsets[line_index].append(current_offset) - self._offset_to_line_info[current_offset] = (line_index, section_offset) - current_offset += 1 + y_offsets_for_line.append(current_y_offset) + new_offset_to_line_info.append((line_index, section_offset)) + current_y_offset += 1 + + # Save the new y offsets for this line + new_line_index_to_offsets.append(y_offsets_for_line) # Replace the range start -> old with the new wrapped lines - print(f"after update, new wrap offsets = \n{new_wrap_offsets!r}") - print(f"length of offsets before = {len(self._wrap_offsets)}") + print( + f"replacing offset_to_line_info[{start_y_offset}:{old_end_y_offset + 1}] with\n{new_offset_to_line_info}" + ) + self._offset_to_line_info[ + start_y_offset : old_end_y_offset + 1 + ] = new_offset_to_line_info + + print(f"AFTER -> offset_to_line={self._offset_to_line_info!r}") + + self._line_index_to_offsets[ + start_line_index : old_end_line_index + 1 + ] = new_line_index_to_offsets + # How much did the edit/rewrap alter the offsets? + new_height = current_y_offset - start_y_offset + offset_delta = new_height - old_height + + # Iterate over all the lines below the edit region, updating offset data. + for line_index in range( + start_line_index + len(new_line_index_to_offsets), + len(self._line_index_to_offsets), + ): + y_offsets_for_line = self._line_index_to_offsets[line_index] + for section_offset, y_offset in enumerate(y_offsets_for_line): + y_offsets_for_line[section_offset] += offset_delta + self._offset_to_line_info[current_y_offset] = ( + line_index, + section_offset, + ) + current_y_offset += 1 + self._wrap_offsets[start_line_index : old_end_line_index + 1] = new_wrap_offsets - print(f"length of offsets after = {len(self._wrap_offsets)}") def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, @@ -204,7 +244,7 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Find the line corresponding to the given y offset in the wrapped document. get_target_document_column = self.get_target_document_column - offset_data = self._offset_to_line_info.get(y) + offset_data = self._offset_to_line_info[y] if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index bb57de5590..5d9bdc4dc4 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -793,9 +793,12 @@ def render_line(self, widget_y: int) -> Strip: if out_of_bounds: return blank_line - print(f"current y_offset = {y_offset!r}") # Get the line corresponding to this offset - line_info = wrapped_document._offset_to_line_info.get(y_offset) + try: + line_info = wrapped_document._offset_to_line_info[y_offset] + except IndexError: + line_info = None + if line_info is None: return blank_line @@ -940,7 +943,7 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) width, _ = self.size - section_width = width - self.gutter_width + section_width = width - self.gutter_width + 1 line.set_length(section_width) text_segments = console.render( line, From 335d2317c2ef648ddda521c0088b43df893fabcc Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 21 Dec 2023 16:37:23 +0000 Subject: [PATCH 060/150] Fixing the calculation which recomputes offset to line mapping --- src/textual/document/_wrapped_document.py | 51 ++++++++--------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index d55b17d069..11d506c6c6 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -83,8 +83,6 @@ def wrap(self, width: int) -> None: self._offset_to_line_info = offset_to_line_info self._line_index_to_offsets = line_index_to_offsets self._wrap_offsets = new_wrap_offsets - print(f"BUILT offset_to_line_info = \n{offset_to_line_info!r}") - print(f"BUILT line_index_to_offsets = \n{line_index_to_offsets!r}") @property def lines(self) -> list[list[str]]: @@ -128,9 +126,6 @@ def wrap_range( # Get all the text on the lines between start and end in document space - start, old_end = sorted((start, old_end)) - start, new_end = sorted((start, new_end)) - start_line_index, _ = start old_end_line_index, _ = old_end new_end_line_index, _ = new_end @@ -142,20 +137,7 @@ def wrap_range( # Clearing old cached data start_y_offset = self._line_index_to_offsets[start_line_index][0] old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] - old_height = old_end_y_offset - start_y_offset - - print("Wrap--") - print( - f"ranges = start = {start_line_index!r}, old_end = {old_end!r}, new_end = {new_end!r}" - ) - # TODO - we're deleting two lines here and sometimes only replacing them - # with one thing. This seems to happen when we delete. - - # Replace the old offsets with the new offsets - # The old offsets are those between start -> old_end inclusive - # The new offsets are those between start -> new_end inclusive - # We'll need to retrieve the offsets from the locations new_lines = self.document.lines[start_line_index : new_end_line_index + 1] new_wrap_offsets: list[int] = [] @@ -166,10 +148,6 @@ def wrap_range( # Add the new offsets between start and new end (the new post-edit offsets) current_y_offset = start_y_offset - - # TODO - I think we cannot just iterate over new_lines here - # If we replace some text, the newlines could be a single line - # But the offsets we need to update for line_index, line in enumerate(new_lines, start_line_index): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) @@ -185,35 +163,40 @@ def wrap_range( new_line_index_to_offsets.append(y_offsets_for_line) # Replace the range start -> old with the new wrapped lines - print( - f"replacing offset_to_line_info[{start_y_offset}:{old_end_y_offset + 1}] with\n{new_offset_to_line_info}" - ) self._offset_to_line_info[ start_y_offset : old_end_y_offset + 1 ] = new_offset_to_line_info - print(f"AFTER -> offset_to_line={self._offset_to_line_info!r}") - self._line_index_to_offsets[ start_line_index : old_end_line_index + 1 ] = new_line_index_to_offsets + # How much did the edit/rewrap alter the offsets? - new_height = current_y_offset - start_y_offset + old_height = old_end_y_offset - start_y_offset + 1 + new_height = len(new_lines) offset_delta = new_height - old_height # Iterate over all the lines below the edit region, updating offset data. + edit_top_line_index = min((start_line_index, new_end_line_index)) + remaining_lines_start = edit_top_line_index + len(new_lines) + for line_index in range( - start_line_index + len(new_line_index_to_offsets), - len(self._line_index_to_offsets), + remaining_lines_start, # from the first line below the edit + len(self._line_index_to_offsets), # to the end of the document ): y_offsets_for_line = self._line_index_to_offsets[line_index] - for section_offset, y_offset in enumerate(y_offsets_for_line): - y_offsets_for_line[section_offset] += offset_delta - self._offset_to_line_info[current_y_offset] = ( + for section_offset in range(len(y_offsets_for_line)): + # Apply the change in offsets + old_offset = y_offsets_for_line[section_offset] + new_offset = old_offset + offset_delta + y_offsets_for_line[section_offset] = new_offset + # Apply the change in line indices + + new_line_info = ( line_index, section_offset, ) - current_y_offset += 1 + self._offset_to_line_info[new_offset] = new_line_info self._wrap_offsets[start_line_index : old_end_line_index + 1] = new_wrap_offsets From 7a822c8ce17ad438f1b557d8fb6d7182b43f55b2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 21 Dec 2023 16:59:26 +0000 Subject: [PATCH 061/150] Leaving comments for future Darren --- src/textual/document/_wrapped_document.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 11d506c6c6..814dae5969 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -138,6 +138,16 @@ def wrap_range( start_y_offset = self._line_index_to_offsets[start_line_index][0] old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] + # TODO when you return + # I think the issue is that the "end" of an edit can be BEFORE the "start" + # of an edit. However, we haven't really taken that into account, and this + # will likely mess with the slicing ranges etc. It could also result in negative + # values appearing while trying to calculate heights which we assume to be positive. + # The two variables defined below are part of the puzzle, although they're probably + # incorrect - I haven't spent enough time looking into this. + edit_top_line_index = min((start_line_index, new_end_line_index)) + edit_bottom_line_index = max((start_line_index, new_end_line_index)) + new_lines = self.document.lines[start_line_index : new_end_line_index + 1] new_wrap_offsets: list[int] = [] @@ -174,10 +184,10 @@ def wrap_range( # How much did the edit/rewrap alter the offsets? old_height = old_end_y_offset - start_y_offset + 1 new_height = len(new_lines) - offset_delta = new_height - old_height + offset_delta = abs(new_height - old_height) + + # Iterate over all the lines below the edit region, updating offset data - # Iterate over all the lines below the edit region, updating offset data. - edit_top_line_index = min((start_line_index, new_end_line_index)) remaining_lines_start = edit_top_line_index + len(new_lines) for line_index in range( @@ -191,11 +201,13 @@ def wrap_range( new_offset = old_offset + offset_delta y_offsets_for_line[section_offset] = new_offset # Apply the change in line indices - new_line_info = ( line_index, section_offset, ) + + # TODO - can we be 100% sure that these indices still exist? + # should we make a new list and rebuild self._offset_to_line_info[new_offset] = new_line_info self._wrap_offsets[start_line_index : old_end_line_index + 1] = new_wrap_offsets From af38ec545ce7aaf18ee31c1f48e4f62875f67ced Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 09:53:06 +0000 Subject: [PATCH 062/150] Normalise wrap update range --- src/textual/document/_wrapped_document.py | 46 +++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 814dae5969..df4e7ef1aa 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -130,13 +130,20 @@ def wrap_range( old_end_line_index, _ = old_end new_end_line_index, _ = new_end - # Can we only rewrap a section of the line starting at the wrap point - # to the left of the edit? There may be something we can do here. However, - # an edit can alter wrap points before it. + top_line_index, old_bottom_line_index = sorted( + (start_line_index, old_end_line_index) + ) + new_bottom_line_index = max((start_line_index, new_end_line_index)) # Clearing old cached data - start_y_offset = self._line_index_to_offsets[start_line_index][0] - old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] + # start_y_offset = self._line_index_to_offsets[start_line_index][0] + # old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] + + # Get the top and bottom of the old edit in terms of y_offsets + # Line index can correspond to many offsets, we should take the minimum. + # TODO - assuming index 0 is the minimum here. + top_y_offset = self._line_index_to_offsets[top_line_index][0] + old_bottom_y_offset = self._line_index_to_offsets[old_bottom_line_index][-1] # TODO when you return # I think the issue is that the "end" of an edit can be BEFORE the "start" @@ -145,10 +152,9 @@ def wrap_range( # values appearing while trying to calculate heights which we assume to be positive. # The two variables defined below are part of the puzzle, although they're probably # incorrect - I haven't spent enough time looking into this. - edit_top_line_index = min((start_line_index, new_end_line_index)) - edit_bottom_line_index = max((start_line_index, new_end_line_index)) - new_lines = self.document.lines[start_line_index : new_end_line_index + 1] + # Get the new range of the edit from top to bottom. + new_lines = self.document.lines[top_line_index : new_bottom_line_index + 1] new_wrap_offsets: list[int] = [] new_line_index_to_offsets: list[list[VerticalOffset]] = [] @@ -156,9 +162,9 @@ def wrap_range( append_wrap_offset = new_wrap_offsets.append width = self._width - # Add the new offsets between start and new end (the new post-edit offsets) - current_y_offset = start_y_offset - for line_index, line in enumerate(new_lines, start_line_index): + # Add the new offsets between the top and new bottom (the new post-edit offsets) + current_y_offset = top_y_offset + for line_index, line in enumerate(new_lines, top_line_index): wrap_offsets = divide_line(line, width) if width else [] append_wrap_offset(wrap_offsets) @@ -174,22 +180,20 @@ def wrap_range( # Replace the range start -> old with the new wrapped lines self._offset_to_line_info[ - start_y_offset : old_end_y_offset + 1 + top_y_offset : old_bottom_y_offset + 1 ] = new_offset_to_line_info self._line_index_to_offsets[ - start_line_index : old_end_line_index + 1 + top_line_index : old_bottom_line_index + 1 ] = new_line_index_to_offsets # How much did the edit/rewrap alter the offsets? - old_height = old_end_y_offset - start_y_offset + 1 + old_height = old_bottom_y_offset - top_y_offset + 1 new_height = len(new_lines) offset_delta = abs(new_height - old_height) - # Iterate over all the lines below the edit region, updating offset data - - remaining_lines_start = edit_top_line_index + len(new_lines) - + # Iterate over all the lines *below* the edit region, updating offset data + remaining_lines_start = top_line_index + len(new_lines) for line_index in range( remaining_lines_start, # from the first line below the edit len(self._line_index_to_offsets), # to the end of the document @@ -207,10 +211,12 @@ def wrap_range( ) # TODO - can we be 100% sure that these indices still exist? - # should we make a new list and rebuild + # should we make a new list and rebuild? self._offset_to_line_info[new_offset] = new_line_info - self._wrap_offsets[start_line_index : old_end_line_index + 1] = new_wrap_offsets + self._wrap_offsets[ + top_line_index : old_bottom_line_index + 1 + ] = new_wrap_offsets def offset_to_location(self, offset: Offset, tab_width: int) -> Location: """Given an offset within the wrapped/visual display of the document, From 7e464c8bdf5e7ab34cc5779bd3d9173f6418998c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 12:14:51 +0000 Subject: [PATCH 063/150] Fixes for mypy --- src/textual/document/_wrapped_document.py | 51 +++++++++++------------ 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index df4e7ef1aa..ea29aa8093 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -64,8 +64,8 @@ def wrap(self, width: int) -> None: self._width = width # We're starting wrapping from scratch, so use fresh - new_wrap_offsets = [] - offset_to_line_info = [] + new_wrap_offsets: list[list[int]] = [] + offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] line_index_to_offsets: list[list[VerticalOffset]] = [] append_wrap_offset = new_wrap_offsets.append @@ -95,7 +95,7 @@ def lines(self) -> list[list[str]]: Returns: A list of lines from the wrapped version of the document. """ - wrapped_lines = [] + wrapped_lines: list[list[str]] = [] append = wrapped_lines.append for line_index, line in enumerate(self.document.lines): divided = Text(line).divide(self._wrap_offsets[line_index]) @@ -156,17 +156,17 @@ def wrap_range( # Get the new range of the edit from top to bottom. new_lines = self.document.lines[top_line_index : new_bottom_line_index + 1] - new_wrap_offsets: list[int] = [] + new_wrap_offsets: list[list[int]] = [] new_line_index_to_offsets: list[list[VerticalOffset]] = [] new_offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] - append_wrap_offset = new_wrap_offsets.append + append_wrap_offsets = new_wrap_offsets.append width = self._width # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset for line_index, line in enumerate(new_lines, top_line_index): wrap_offsets = divide_line(line, width) if width else [] - append_wrap_offset(wrap_offsets) + append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line y_offsets_for_line: list[int] = [] @@ -189,30 +189,27 @@ def wrap_range( # How much did the edit/rewrap alter the offsets? old_height = old_bottom_y_offset - top_y_offset + 1 - new_height = len(new_lines) - offset_delta = abs(new_height - old_height) + new_height = len(new_offset_to_line_info) + + offset_shift = new_height - old_height + line_shift = new_bottom_line_index - old_bottom_line_index + + # Update the line info at all offsets below the edit region. + for y_offset in range( + top_y_offset + new_height, len(self._offset_to_line_info) + ): + old_line_index, section_offset = self._offset_to_line_info[y_offset] + new_line_index = old_line_index + line_shift + new_line_info = (new_line_index, section_offset) + self._offset_to_line_info[y_offset] = new_line_info - # Iterate over all the lines *below* the edit region, updating offset data - remaining_lines_start = top_line_index + len(new_lines) + # Update the offsets at all lines below the edit region for line_index in range( - remaining_lines_start, # from the first line below the edit - len(self._line_index_to_offsets), # to the end of the document + top_line_index + len(new_lines), len(self._line_index_to_offsets) ): - y_offsets_for_line = self._line_index_to_offsets[line_index] - for section_offset in range(len(y_offsets_for_line)): - # Apply the change in offsets - old_offset = y_offsets_for_line[section_offset] - new_offset = old_offset + offset_delta - y_offsets_for_line[section_offset] = new_offset - # Apply the change in line indices - new_line_info = ( - line_index, - section_offset, - ) - - # TODO - can we be 100% sure that these indices still exist? - # should we make a new list and rebuild? - self._offset_to_line_info[new_offset] = new_line_info + old_offsets = self._line_index_to_offsets[line_index] + new_offsets = [offset + offset_shift for offset in old_offsets] + self._line_index_to_offsets[line_index] = new_offsets self._wrap_offsets[ top_line_index : old_bottom_line_index + 1 From 56b4df7335268263912e4b48cb0cd5ba019f50ff Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 15:29:55 +0000 Subject: [PATCH 064/150] Fix clamping --- src/textual/document/_wrapped_document.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index ea29aa8093..7d7ba17b50 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -23,6 +23,7 @@ class WrappedDocument: def __init__( self, document: DocumentBase, + width: int = 0, ) -> None: """Construct a WrappedDocument. @@ -31,6 +32,7 @@ def __init__( Args: document: The document to wrap. + width: The width to wrap at. """ self.document = document """The document wrapping is performed on.""" @@ -49,11 +51,11 @@ def __init__( # self._offset_to_section_offset: dict[int, int] = {} # """Maps y_offsets to the offsets of the section within the line.""" - self._width: int = 0 + self._width: int = width """The width the document is currently wrapped at. This will correspond with the value last passed into the `wrap` method.""" - self.wrap(self._width) + self.wrap(width) def wrap(self, width: int) -> None: """Wrap and cache all lines in the document. @@ -242,7 +244,13 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Find the line corresponding to the given y offset in the wrapped document. get_target_document_column = self.get_target_document_column - offset_data = self._offset_to_line_info[y] + + try: + offset_data = self._offset_to_line_info[y] + except IndexError: + # y-offset is too large + offset_data = self._offset_to_line_info[-1] + if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( From e04300a24a551dc128a0443a13b07564adafd242 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 16:01:25 +0000 Subject: [PATCH 065/150] Fixing some tests - snapshots fail since rendering still needs work --- src/textual/document/_wrapped_document.py | 30 ++++++++++------------ tests/text_area/test_selection_bindings.py | 1 + 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 7d7ba17b50..b3165845c9 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -12,7 +12,7 @@ from textual._cells import cell_width_to_column_index from textual.document._document import DocumentBase, Location -from textual.geometry import Offset +from textual.geometry import Offset, clamp VerticalOffset = int LineIndex = int @@ -132,29 +132,27 @@ def wrap_range( old_end_line_index, _ = old_end new_end_line_index, _ = new_end + # Although end users should not be able to edit invalid ranges via a TextArea, + # programmers can pass whatever they wish to the edit API, so we need to clamp + # the edit ranges here to ensure we only attempt to update within the bounds + # of the wrapped document. + old_max_index = len(self._line_index_to_offsets) - 1 + new_max_index = self.document.line_count - 1 + + start_line_index = clamp( + start_line_index, 0, min((old_max_index, new_max_index)) + ) + old_end_line_index = clamp(old_end_line_index, 0, old_max_index) + new_end_line_index = clamp(new_end_line_index, 0, new_max_index) + top_line_index, old_bottom_line_index = sorted( (start_line_index, old_end_line_index) ) new_bottom_line_index = max((start_line_index, new_end_line_index)) - # Clearing old cached data - # start_y_offset = self._line_index_to_offsets[start_line_index][0] - # old_end_y_offset = self._line_index_to_offsets[old_end_line_index][-1] - - # Get the top and bottom of the old edit in terms of y_offsets - # Line index can correspond to many offsets, we should take the minimum. - # TODO - assuming index 0 is the minimum here. top_y_offset = self._line_index_to_offsets[top_line_index][0] old_bottom_y_offset = self._line_index_to_offsets[old_bottom_line_index][-1] - # TODO when you return - # I think the issue is that the "end" of an edit can be BEFORE the "start" - # of an edit. However, we haven't really taken that into account, and this - # will likely mess with the slicing ranges etc. It could also result in negative - # values appearing while trying to calculate heights which we assume to be positive. - # The two variables defined below are part of the puzzle, although they're probably - # incorrect - I haven't spent enough time looking into this. - # Get the new range of the edit from top to bottom. new_lines = self.document.lines[top_line_index : new_bottom_line_index + 1] diff --git a/tests/text_area/test_selection_bindings.py b/tests/text_area/test_selection_bindings.py index 76d4586df4..59899a318b 100644 --- a/tests/text_area/test_selection_bindings.py +++ b/tests/text_area/test_selection_bindings.py @@ -276,6 +276,7 @@ async def test_cursor_page_up(): ) +@pytest.mark.xfail(reason="still to integrate navigator") async def test_cursor_vertical_movement_visual_alignment_snapping(): """When you move the cursor vertically, it should stay vertically aligned even when double-width characters are used.""" From 103ea86ab219f5126f898397d4f250fb42258b81 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 16:20:27 +0000 Subject: [PATCH 066/150] Add navigator attribute to TextArea --- src/textual/widgets/_text_area.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 5d9bdc4dc4..42abf3f4ef 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -22,6 +22,7 @@ Selection, _utf8_encode, ) +from textual.document._document_navigator import DocumentNavigator from textual.document._languages import BUILTIN_LANGUAGES from textual.document._syntax_aware_document import ( SyntaxAwareDocument, @@ -336,6 +337,8 @@ def __init__( self.wrapped_document = WrappedDocument(self.document) """The wrapped view of the document.""" + self._navigator = DocumentNavigator(self.wrapped_document) + self._theme: TextAreaTheme | None = None """The `TextAreaTheme` corresponding to the set theme name. When the `theme` reactive is set as a string, the watcher will update this attribute to the From 424cf614ec5e59a50205be636d75e9da3dfe243a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 16:29:12 +0000 Subject: [PATCH 067/150] Use navigator for moving cursor right --- src/textual/widgets/_text_area.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 42abf3f4ef..8b065466ff 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -338,6 +338,8 @@ def __init__( """The wrapped view of the document.""" self._navigator = DocumentNavigator(self.wrapped_document) + """Queried to determine where the cursor should move given a navigation + action, accounting for wrapping etc.""" self._theme: TextAreaTheme | None = None """The `TextAreaTheme` corresponding to the set theme name. When the `theme` @@ -1425,12 +1427,7 @@ def get_cursor_right_location(self) -> Location: Returns: the location the cursor will move to if it moves right. """ - if self.cursor_at_end_of_text: - return self.selection.end - cursor_row, cursor_column = self.selection.end - target_row = cursor_row + 1 if self.cursor_at_end_of_line else cursor_row - target_column = 0 if self.cursor_at_end_of_line else cursor_column + 1 - return target_row, target_column + return self._navigator.right(self.cursor_location) def action_cursor_down(self, select: bool = False) -> None: """Move the cursor down one cell. From 8be8b789ae928e65a08c6dc85440d7116d34007a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 17:09:55 +0000 Subject: [PATCH 068/150] Integrating navigator for basic movement --- src/textual/document/_document_navigator.py | 4 +- src/textual/document/_wrapped_document.py | 2 + src/textual/widgets/_text_area.py | 53 ++++----------------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index afef8213c5..893772e819 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -145,7 +145,7 @@ def is_end_of_document(self, location: Location) -> bool: location ) - def left(self, location: Location) -> Location: + def get_location_left(self, location: Location) -> Location: if location == (0, 0): return 0, 0 @@ -155,7 +155,7 @@ def left(self, location: Location) -> Location: target_column = column - 1 if column != 0 else length_of_row_above return target_row, target_column - def right(self, location: Location) -> Location: + def get_location_right(self, location: Location) -> Location: if self.is_end_of_document(location): return location row, column = location diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index b3165845c9..0e88033c9d 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -84,6 +84,8 @@ def wrap(self, width: int) -> None: self._offset_to_line_info = offset_to_line_info self._line_index_to_offsets = line_index_to_offsets + + print(f"wrap offsets are now {new_wrap_offsets}") self._wrap_offsets = new_wrap_offsets @property diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8b065466ff..c6e23be739 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -666,6 +666,7 @@ def _set_document(self, text: str, language: str | None) -> None: self.document = document self.wrapped_document = WrappedDocument(document) + self._navigator = DocumentNavigator(self.wrapped_document) self._build_highlight_map() @property @@ -709,17 +710,6 @@ def _rewrap(self) -> None: self.wrapped_document.wrap(available_text_width) log.debug(f"re-wrapping at width {available_text_width!r}") - def load_document(self, document: DocumentBase) -> None: - """Load a document into the TextArea. - - Args: - document: The document to load into the TextArea. - """ - self.document = document - self.move_cursor((0, 0)) - self._refresh_size() - self._rewrap() - @property def is_syntax_aware(self) -> bool: """True if the TextArea is currently syntax aware - i.e. it's parsing document content.""" @@ -1022,14 +1012,10 @@ def edit(self, edit: Edit) -> EditResult: Data relating to the edit that may be useful. The data returned may be different depending on the edit performed. """ - print("doing edit") result = edit.do(self) self._refresh_size() edit.after(self) self._build_highlight_map() - print(f"edit.from_location = {edit.from_location!r}") - print(f"edit.to_location = {edit.to_location!r}") - print(f"result.end_location = {result.end_location!r}") self.wrapped_document.wrap_range( edit.from_location, edit.to_location, result.end_location ) @@ -1393,8 +1379,8 @@ def action_cursor_left(self, select: bool = False) -> None: Args: select: If True, select the text while moving. """ - new_cursor_location = self.get_cursor_left_location() - self.move_cursor(new_cursor_location, select=select) + target = self.get_cursor_left_location() + self.move_cursor(target, select=select) def get_cursor_left_location(self) -> Location: """Get the location the cursor will move to if it moves left. @@ -1402,13 +1388,7 @@ def get_cursor_left_location(self) -> Location: Returns: The location of the cursor if it moves left. """ - if self.cursor_at_start_of_text: - return 0, 0 - cursor_row, cursor_column = self.selection.end - length_of_row_above = len(self.document[cursor_row - 1]) - target_row = cursor_row if cursor_column != 0 else cursor_row - 1 - target_column = cursor_column - 1 if cursor_column != 0 else length_of_row_above - return target_row, target_column + return self._navigator.get_location_left(self.cursor_location) def action_cursor_right(self, select: bool = False) -> None: """Move the cursor one location to the right. @@ -1427,7 +1407,7 @@ def get_cursor_right_location(self) -> Location: Returns: the location the cursor will move to if it moves right. """ - return self._navigator.right(self.cursor_location) + return self._navigator.get_location_right(self.cursor_location) def action_cursor_down(self, select: bool = False) -> None: """Move the cursor down one cell. @@ -1444,17 +1424,9 @@ def get_cursor_down_location(self) -> Location: Returns: The location the cursor will move to if it moves down. """ - cursor_row, cursor_column = self.selection.end - if self.cursor_at_last_line: - return cursor_row, len(self.document[cursor_row]) - - target_row = min(self.document.line_count - 1, cursor_row + 1) - # Attempt to snap last intentional cell length - target_column = self.cell_width_to_column_index( - self._last_intentional_cell_width, target_row + return self._navigator.get_location_below( + self.cursor_location, self.indent_width ) - target_column = clamp(target_column, 0, len(self.document[target_row])) - return target_row, target_column def action_cursor_up(self, select: bool = False) -> None: """Move the cursor up one cell. @@ -1471,16 +1443,9 @@ def get_cursor_up_location(self) -> Location: Returns: The location the cursor will move to if it moves up. """ - if self.cursor_at_first_line: - return 0, 0 - cursor_row, cursor_column = self.selection.end - target_row = max(0, cursor_row - 1) - # Attempt to snap last intentional cell length - target_column = self.cell_width_to_column_index( - self._last_intentional_cell_width, target_row + return self._navigator.get_location_above( + self.cursor_location, self.indent_width ) - target_column = clamp(target_column, 0, len(self.document[target_row])) - return target_row, target_column def action_cursor_line_end(self, select: bool = False) -> None: """Move the cursor to the end of the line.""" From ca9ddd50cfcb96ff211ca1f87de4ef096224cd53 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 28 Dec 2023 20:03:00 +0000 Subject: [PATCH 069/150] Avoid doing offset/line shift loops when there is no shifting to be done --- src/textual/document/_wrapped_document.py | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 0e88033c9d..74d74b3007 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -197,21 +197,23 @@ def wrap_range( line_shift = new_bottom_line_index - old_bottom_line_index # Update the line info at all offsets below the edit region. - for y_offset in range( - top_y_offset + new_height, len(self._offset_to_line_info) - ): - old_line_index, section_offset = self._offset_to_line_info[y_offset] - new_line_index = old_line_index + line_shift - new_line_info = (new_line_index, section_offset) - self._offset_to_line_info[y_offset] = new_line_info + if line_shift: + for y_offset in range( + top_y_offset + new_height, len(self._offset_to_line_info) + ): + old_line_index, section_offset = self._offset_to_line_info[y_offset] + new_line_index = old_line_index + line_shift + new_line_info = (new_line_index, section_offset) + self._offset_to_line_info[y_offset] = new_line_info # Update the offsets at all lines below the edit region - for line_index in range( - top_line_index + len(new_lines), len(self._line_index_to_offsets) - ): - old_offsets = self._line_index_to_offsets[line_index] - new_offsets = [offset + offset_shift for offset in old_offsets] - self._line_index_to_offsets[line_index] = new_offsets + if offset_shift: + for line_index in range( + top_line_index + len(new_lines), len(self._line_index_to_offsets) + ): + old_offsets = self._line_index_to_offsets[line_index] + new_offsets = [offset + offset_shift for offset in old_offsets] + self._line_index_to_offsets[line_index] = new_offsets self._wrap_offsets[ top_line_index : old_bottom_line_index + 1 From f15ca05a080bb07318b03306f1e4ac6e3b77a693 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 10:21:22 +0000 Subject: [PATCH 070/150] Making space for cursor only at line end --- src/textual/widgets/_text_area.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index c6e23be739..c4279cc73e 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -809,7 +809,7 @@ def render_line(self, widget_y: int) -> Strip: line.tab_size = self.indent_width virtual_width, virtual_height = self.virtual_size expanded_width = max(virtual_width, self.size.width) - # line.set_length(expanded_length) + line.set_length(line.cell_len + 1) # Make space for cursor at end. selection = self.selection start, end = selection @@ -938,7 +938,7 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) width, _ = self.size - section_width = width - self.gutter_width + 1 + section_width = width - self.gutter_width line.set_length(section_width) text_segments = console.render( line, From 9eba82a296601b92c8b6bdd1ec836caa78a3b4d3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 13:21:49 +0000 Subject: [PATCH 071/150] Fix some width and wrapping issues, and corresponding style issues --- src/textual/widgets/_text_area.py | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index c4279cc73e..19c1252e2f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -87,6 +87,7 @@ class TextArea(ScrollView, can_focus=True): TextArea { width: 1fr; height: 1fr; + overflow-y: scroll; } """ @@ -331,16 +332,18 @@ def __init__( self._highlight_query: "Query" | None = None """The query that's currently being used for highlighting.""" - self.document: DocumentBase = Document(text) + self.document: DocumentBase """The document this widget is currently editing.""" - self.wrapped_document = WrappedDocument(self.document) + self.wrapped_document: WrappedDocument """The wrapped view of the document.""" - self._navigator = DocumentNavigator(self.wrapped_document) + self._navigator: DocumentNavigator """Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.""" + self._set_document(text, language) + self._theme: TextAreaTheme | None = None """The `TextAreaTheme` corresponding to the set theme name. When the `theme` reactive is set as a string, the watcher will update this attribute to the @@ -668,6 +671,8 @@ def _set_document(self, text: str, language: str | None) -> None: self.wrapped_document = WrappedDocument(document) self._navigator = DocumentNavigator(self.wrapped_document) self._build_highlight_map() + self.move_cursor((0, 0)) + self._rewrap_and_refresh_virtual_size() @property def _visible_line_indices(self) -> tuple[int, int]: @@ -694,20 +699,21 @@ def load_text(self, text: str) -> None: text: The text to load into the TextArea. """ self._set_document(text, self.language) - self.move_cursor((0, 0)) - self._refresh_size() def _on_resize(self) -> None: - self._rewrap() + self._rewrap_and_refresh_virtual_size() def _watch_wrap(self) -> None: - self._rewrap() + self._rewrap_and_refresh_virtual_size() - def _rewrap(self) -> None: + def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: width, _ = self.size - available_text_width = width - self.gutter_width - 1 + available_text_width = ( + width - self.gutter_width - self.styles.scrollbar_size_vertical + ) self.wrapped_document.wrap(available_text_width) + self._refresh_size() log.debug(f"re-wrapping at width {available_text_width!r}") @property @@ -759,11 +765,11 @@ def _yield_character_locations_reverse( def _refresh_size(self) -> None: """Update the virtual size of the TextArea.""" if self.wrap: - return - - width, height = self.document.get_size(self.indent_width) - # +1 width to make space for the cursor resting at the end of the line - self.virtual_size = Size(width + self.gutter_width + 1, height) + self.virtual_size = Size(0, self.wrapped_document.height) + else: + # +1 width to make space for the cursor resting at the end of the line + width, height = self.document.get_size(self.indent_width) + self.virtual_size = Size(width + self.gutter_width + 1, height) def render_line(self, widget_y: int) -> Strip: """Render a single line of the TextArea. Called by Textual. @@ -808,7 +814,6 @@ def render_line(self, widget_y: int) -> Strip: line_character_count = len(line) line.tab_size = self.indent_width virtual_width, virtual_height = self.virtual_size - expanded_width = max(virtual_width, self.size.width) line.set_length(line.cell_len + 1) # Make space for cursor at end. selection = self.selection @@ -938,7 +943,7 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) width, _ = self.size - section_width = width - self.gutter_width + section_width = width - self.gutter_width - self.scrollbar_size_vertical line.set_length(section_width) text_segments = console.render( line, @@ -955,11 +960,9 @@ def render_line(self, widget_y: int) -> Strip: # Stylize the line the cursor is currently on. if cursor_row == line_index: - text_strip = text_strip.extend_cell_length(section_width, cursor_line_style) + text_strip = text_strip.apply_style(cursor_line_style) else: - text_strip = text_strip.extend_cell_length( - section_width, theme.base_style if theme else None - ) + text_strip = text_strip.apply_style(theme.base_style if theme else None) strip = Strip.join([gutter_strip, text_strip]).simplify() @@ -1013,12 +1016,12 @@ def edit(self, edit: Edit) -> EditResult: may be different depending on the edit performed. """ result = edit.do(self) - self._refresh_size() - edit.after(self) - self._build_highlight_map() self.wrapped_document.wrap_range( edit.from_location, edit.to_location, result.end_location ) + self._refresh_size() + edit.after(self) + self._build_highlight_map() self.post_message(self.Changed(self)) return result From 61f748254c572f97b3e0ac9a0c7dee41b85c3e86 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 14:59:22 +0000 Subject: [PATCH 072/150] Fix non-wrapping rendering --- src/textual/widgets/_text_area.py | 71 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index c4279cc73e..421cf4568b 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -87,6 +87,7 @@ class TextArea(ScrollView, can_focus=True): TextArea { width: 1fr; height: 1fr; + overflow-y: scroll; } """ @@ -331,16 +332,18 @@ def __init__( self._highlight_query: "Query" | None = None """The query that's currently being used for highlighting.""" - self.document: DocumentBase = Document(text) + self.document: DocumentBase """The document this widget is currently editing.""" - self.wrapped_document = WrappedDocument(self.document) + self.wrapped_document: WrappedDocument """The wrapped view of the document.""" - self._navigator = DocumentNavigator(self.wrapped_document) + self._navigator: DocumentNavigator """Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.""" + self._set_document(text, language) + self._theme: TextAreaTheme | None = None """The `TextAreaTheme` corresponding to the set theme name. When the `theme` reactive is set as a string, the watcher will update this attribute to the @@ -668,6 +671,8 @@ def _set_document(self, text: str, language: str | None) -> None: self.wrapped_document = WrappedDocument(document) self._navigator = DocumentNavigator(self.wrapped_document) self._build_highlight_map() + self.move_cursor((0, 0)) + self._rewrap_and_refresh_virtual_size() @property def _visible_line_indices(self) -> tuple[int, int]: @@ -694,20 +699,21 @@ def load_text(self, text: str) -> None: text: The text to load into the TextArea. """ self._set_document(text, self.language) - self.move_cursor((0, 0)) - self._refresh_size() def _on_resize(self) -> None: - self._rewrap() + self._rewrap_and_refresh_virtual_size() def _watch_wrap(self) -> None: - self._rewrap() + self._rewrap_and_refresh_virtual_size() - def _rewrap(self) -> None: + def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: width, _ = self.size - available_text_width = width - self.gutter_width - 1 + available_text_width = ( + width - self.gutter_width - self.styles.scrollbar_size_vertical + ) self.wrapped_document.wrap(available_text_width) + self._refresh_size() log.debug(f"re-wrapping at width {available_text_width!r}") @property @@ -759,11 +765,11 @@ def _yield_character_locations_reverse( def _refresh_size(self) -> None: """Update the virtual size of the TextArea.""" if self.wrap: - return - - width, height = self.document.get_size(self.indent_width) - # +1 width to make space for the cursor resting at the end of the line - self.virtual_size = Size(width + self.gutter_width + 1, height) + self.virtual_size = Size(0, self.wrapped_document.height) + else: + # +1 width to make space for the cursor resting at the end of the line + width, height = self.document.get_size(self.indent_width) + self.virtual_size = Size(width + self.gutter_width + 1, height) def render_line(self, widget_y: int) -> Strip: """Render a single line of the TextArea. Called by Textual. @@ -808,7 +814,6 @@ def render_line(self, widget_y: int) -> Strip: line_character_count = len(line) line.tab_size = self.indent_width virtual_width, virtual_height = self.virtual_size - expanded_width = max(virtual_width, self.size.width) line.set_length(line.cell_len + 1) # Make space for cursor at end. selection = self.selection @@ -846,7 +851,6 @@ def render_line(self, widget_y: int) -> Strip: if line_character_count == 0 and line_index != cursor_row: # A simple highlight to show empty lines are included in the selection line = Text("▌", end="", style=Style(color=selection_style.bgcolor)) - line.set_length(self.virtual_size.width) else: if line_index == selection_top_row == selection_bottom_row: # Selection within a single line @@ -929,25 +933,34 @@ def render_line(self, widget_y: int) -> Strip: # We should cache sections with the edit counts. wrap_offsets = wrapped_document.get_offsets(line_index) - # Join and return the gutter and the visible portion of this line if wrap_offsets: sections = line.divide(wrap_offsets) # TODO cache result with edit count line = sections[section_offset] - # Render the gutter and the text of this line + if self.wrap: + # If we're wrapping, the line should be wrapped to the width available for the text. + # That is the width of the widget minus the gutter width and scrollbar width. + target_width = self.size.width - self.gutter_width + else: + # If we're not wrapping, then we want to expand the line such that it's equal to the virtual width. + target_width = virtual_width - self.gutter_width + + # Set the width of this section to the target width. + line.set_length(target_width) + console = self.app.console gutter_segments = console.render(gutter) - width, _ = self.size - section_width = width - self.gutter_width - line.set_length(section_width) text_segments = console.render( line, - console.options.update_width(section_width), + console.options.update_width( + target_width + ), # TODO - why do we need to update the width here? DO WE? ) - # Crop the line to show only the visible part (some may be scrolled out of view) gutter_strip = Strip(gutter_segments, cell_length=gutter_width) text_strip = Strip(text_segments) + + # Crop the line to show only the visible part (some may be scrolled out of view) if not self.wrap: text_strip = text_strip.crop( scroll_x, scroll_x + virtual_width - gutter_width @@ -955,11 +968,9 @@ def render_line(self, widget_y: int) -> Strip: # Stylize the line the cursor is currently on. if cursor_row == line_index: - text_strip = text_strip.extend_cell_length(section_width, cursor_line_style) + text_strip = text_strip.apply_style(cursor_line_style) else: - text_strip = text_strip.extend_cell_length( - section_width, theme.base_style if theme else None - ) + text_strip = text_strip.apply_style(theme.base_style if theme else None) strip = Strip.join([gutter_strip, text_strip]).simplify() @@ -1013,12 +1024,12 @@ def edit(self, edit: Edit) -> EditResult: may be different depending on the edit performed. """ result = edit.do(self) - self._refresh_size() - edit.after(self) - self._build_highlight_map() self.wrapped_document.wrap_range( edit.from_location, edit.to_location, result.end_location ) + self._refresh_size() + edit.after(self) + self._build_highlight_map() self.post_message(self.Changed(self)) return result From d50fa8d020c42eec45d6a46812d1ed45a1eeddf4 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 15:03:17 +0000 Subject: [PATCH 073/150] Fix wrapping off by one --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 421cf4568b..3ccacc0fc3 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -710,7 +710,7 @@ def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: width, _ = self.size available_text_width = ( - width - self.gutter_width - self.styles.scrollbar_size_vertical + width - self.gutter_width - self.styles.scrollbar_size_vertical - 1 ) self.wrapped_document.wrap(available_text_width) self._refresh_size() From dffb488d5475bb78839bb9503bd24f9044db416b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 15:12:59 +0000 Subject: [PATCH 074/150] Simplifying text rendering --- src/textual/widgets/_text_area.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 3ccacc0fc3..abdc7de403 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -950,12 +950,7 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) - text_segments = console.render( - line, - console.options.update_width( - target_width - ), # TODO - why do we need to update the width here? DO WE? - ) + text_segments = console.render(line) gutter_strip = Strip(gutter_segments, cell_length=gutter_width) text_strip = Strip(text_segments) From 016ae3b62623bff16082443036a8627faf4a3c60 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 15:15:27 +0000 Subject: [PATCH 075/150] Unsimplifying text rendering --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index abdc7de403..4ca561ba5e 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -950,7 +950,7 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) - text_segments = console.render(line) + text_segments = console.render(line, console.options.update_width(target_width)) gutter_strip = Strip(gutter_segments, cell_length=gutter_width) text_strip = Strip(text_segments) From fbeabd518d5457ac80d38fb4c6c9b943b481a176 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 15:51:55 +0000 Subject: [PATCH 076/150] Fix an off by one --- pyproject.toml | 4 ++-- src/textual/widgets/_text_area.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e2aacb3975..e41c99a780 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,9 +41,9 @@ include = [ [tool.poetry.dependencies] python = "^3.8" -rich = ">=13.3.3" markdown-it-py = { extras = ["plugins", "linkify"], version = ">=2.1.0" } -#rich = {path="../rich", develop=true} +#rich = ">=13.3.3" +rich = {path="../rich", develop=true} typing-extensions = "^4.4.0" tree-sitter = { version = "^0.20.1", optional = true } tree_sitter_languages = { version = ">=1.7.0", optional = true } diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 4ca561ba5e..d3d178b669 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -710,7 +710,7 @@ def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: width, _ = self.size available_text_width = ( - width - self.gutter_width - self.styles.scrollbar_size_vertical - 1 + width - self.gutter_width - self.styles.scrollbar_size_vertical ) self.wrapped_document.wrap(available_text_width) self._refresh_size() From 0c0d979a6ad32219e681206143a8b8d4988871c9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 3 Jan 2024 16:51:46 +0000 Subject: [PATCH 077/150] Supporting Home and End in the wrapped document --- src/textual/document/_document_navigator.py | 9 +++++++ src/textual/document/_wrapped_document.py | 2 +- src/textual/widgets/_text_area.py | 27 +++------------------ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 893772e819..3da7c2da7f 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -285,6 +285,15 @@ def get_location_home(self, location: Location) -> Location: return line_index, wrap_offsets[next_offset_left - 1] else: # No wrapping to consider, go to the start of the document line + line = self._wrapped_document.document[line_index] + first_non_whitespace = 0 + for index, code_point in enumerate(line): + if not code_point.isspace(): + first_non_whitespace = index + break + + if column_offset <= first_non_whitespace and column_offset != 0: + return line_index, first_non_whitespace return line_index, 0 # TODO - we need to implement methods for going page up and page down diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 74d74b3007..1fa8873b10 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -65,7 +65,7 @@ def wrap(self, width: int) -> None: """ self._width = width - # We're starting wrapping from scratch, so use fresh + # We're starting wrapping from scratch new_wrap_offsets: list[list[int]] = [] offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] line_index_to_offsets: list[list[VerticalOffset]] = [] diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index d3d178b669..217fb219b9 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1464,29 +1464,12 @@ def get_cursor_line_end_location(self) -> Location: Returns: The (row, column) location of the end of the cursors current line. """ - start, end = self.selection - cursor_row, cursor_column = end - target_column = len(self.document[cursor_row]) - return cursor_row, target_column + return self._navigator.get_location_end(self.cursor_location) def action_cursor_line_start(self, select: bool = False) -> None: """Move the cursor to the start of the line.""" - - cursor_row, cursor_column = self.cursor_location - line = self.document[cursor_row] - - first_non_whitespace = 0 - for index, code_point in enumerate(line): - if not code_point.isspace(): - first_non_whitespace = index - break - - if cursor_column <= first_non_whitespace and cursor_column != 0: - target = self.get_cursor_line_start_location() - self.move_cursor(target, select=select) - else: - target = cursor_row, first_non_whitespace - self.move_cursor(target, select=select) + target = self.get_cursor_line_start_location() + self.move_cursor(target, select=select) def get_cursor_line_start_location(self) -> Location: """Get the location of the start of the current line. @@ -1494,9 +1477,7 @@ def get_cursor_line_start_location(self) -> Location: Returns: The (row, column) location of the start of the cursors current line. """ - _start, end = self.selection - cursor_row, _cursor_column = end - return cursor_row, 0 + return self._navigator.get_location_home(self.cursor_location) def action_cursor_word_left(self, select: bool = False) -> None: """Move the cursor left by a single word, skipping trailing whitespace. From f39220c540cc9051a249b6eca7df93da732f6b37 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 10:31:03 +0000 Subject: [PATCH 078/150] Make click targeting work. However, we are still at the stage where scrolling the cursor into view is off as we havent yet accounted for wrapping. --- src/textual/widgets/_text_area.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 217fb219b9..7150e0010f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1078,16 +1078,12 @@ def get_target_document_location(self, event: MouseEvent) -> Location: """ scroll_x, scroll_y = self.scroll_offset target_x = event.x - self.gutter_width + scroll_x - self.gutter.left - target_x = max(target_x, 0) - target_row = clamp( - event.y + scroll_y - self.gutter.top, - 0, - self.document.line_count - 1, + target_y = event.y + scroll_y - self.gutter.top + location = self.wrapped_document.offset_to_location( + Offset(target_x, target_y), self.indent_width ) - target_column = self.cell_width_to_column_index(target_x, target_row) - return target_row, target_column + return location - # --- Lower level event/key handling @property def gutter_width(self) -> int: """The width of the gutter (the left column containing line numbers). From 6282735eb89f0d8f74d7ba189857900ef537ded2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 11:28:49 +0000 Subject: [PATCH 079/150] Add location to offset conversion --- src/textual/document/_wrapped_document.py | 36 ++++++++++++++++++++++- src/textual/widgets/_text_area.py | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 1fa8873b10..345441012a 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -7,11 +7,14 @@ """ from __future__ import annotations +from bisect import bisect + from rich._wrap import divide_line from rich.text import Text -from textual._cells import cell_width_to_column_index +from textual._cells import cell_len, cell_width_to_column_index from textual.document._document import DocumentBase, Location +from textual.expand_tabs import expand_tabs_inline from textual.geometry import Offset, clamp VerticalOffset = int @@ -269,6 +272,37 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # Offset doesn't match any line => land on bottom wrapped line return location + def location_to_offset(self, location: Location, tab_width: int) -> Offset: + """ + Convert a location in the document to an offset within the wrapped/visual display of the document. + + Args: + location: The location in the document. + tab_width: The maximum width of tab characters in the document. + + Returns: + The Offset in the document's visual display corresponding to the given location. + """ + line_index, column_index = location + + # Clamp the line index to the bounds of the document + line_index = clamp(line_index, 0, len(self._line_index_to_offsets)) + + # Find the section index of this location, so that we know which y_offset to use + section_start_columns = [0, *self.get_offsets(line_index)] + section_index = bisect(section_start_columns, column_index) + + # Get the y-offsets corresponding to this line index + y_offsets = self._line_index_to_offsets[line_index] + section_column_index = column_index - section_start_columns[section_index] + + section = self.get_sections(section_index)[section_index] + x_offset = cell_len( + expand_tabs_inline(section[:section_column_index], tab_width) + ) + + return Offset(x_offset, y_offsets[section_index]) + def get_target_document_column( self, line_index: int, diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 7150e0010f..1c1659f372 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -416,6 +416,8 @@ def _watch_selection(self, selection: Selection) -> None: except IndexError: character = "" + # + # Record the location of a matching closing/opening bracket. match_location = self.find_matching_bracket(character, cursor_location) self._matching_bracket_location = match_location From 2e0e4e3ae2a907449895fa95378e09d55d63d77e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 11:44:46 +0000 Subject: [PATCH 080/150] Click targeting, scrolling --- src/textual/document/_wrapped_document.py | 9 +++++---- src/textual/widgets/_text_area.py | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 345441012a..27b1e0f05e 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -7,7 +7,7 @@ """ from __future__ import annotations -from bisect import bisect +from bisect import bisect_right from rich._wrap import divide_line from rich.text import Text @@ -289,14 +289,15 @@ def location_to_offset(self, location: Location, tab_width: int) -> Offset: line_index = clamp(line_index, 0, len(self._line_index_to_offsets)) # Find the section index of this location, so that we know which y_offset to use - section_start_columns = [0, *self.get_offsets(line_index)] - section_index = bisect(section_start_columns, column_index) + wrap_offsets = self.get_offsets(line_index) + section_start_columns = [0, *wrap_offsets] + section_index = bisect_right(wrap_offsets, column_index) # Get the y-offsets corresponding to this line index y_offsets = self._line_index_to_offsets[line_index] section_column_index = column_index - section_start_columns[section_index] - section = self.get_sections(section_index)[section_index] + section = self.get_sections(line_index)[section_index] x_offset = cell_len( expand_tabs_inline(section[:section_column_index], tab_width) ) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 1c1659f372..238f296ddd 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -342,6 +342,8 @@ def __init__( """Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.""" + self._cursor_offset = (0, 0) + self._set_document(text, language) self._theme: TextAreaTheme | None = None @@ -407,8 +409,14 @@ def _build_highlight_map(self) -> None: def _watch_selection(self, selection: Selection) -> None: """When the cursor moves, scroll it into view.""" - self.scroll_cursor_visible() + # Find the visual offset of the cursor in the document cursor_location = selection.end + + self._cursor_offset = self.wrapped_document.location_to_offset( + cursor_location, self.indent_width + ) + self.scroll_cursor_visible() + cursor_row, cursor_column = cursor_location try: @@ -416,8 +424,6 @@ def _watch_selection(self, selection: Selection) -> None: except IndexError: character = "" - # - # Record the location of a matching closing/opening bracket. match_location = self.find_matching_bracket(character, cursor_location) self._matching_bracket_location = match_location @@ -426,7 +432,7 @@ def _watch_selection(self, selection: Selection) -> None: if match_row in range(*self._visible_line_indices): self.refresh_lines(match_row) - self.app.cursor_position = self.cursor_screen_offset + self.app.cursor_position = self._cursor_offset self.post_message(self.SelectionChanged(selection, self)) def find_matching_bracket( @@ -1209,11 +1215,9 @@ def scroll_cursor_visible( Returns: The offset that was scrolled to bring the cursor into view. """ - row, column = self.selection.end - text = self.document[row][:column] - column_offset = cell_len(expand_tabs_inline(text, self.indent_width)) + x, y = self._cursor_offset scroll_offset = self.scroll_to_region( - Region(x=column_offset, y=row, width=3, height=1), + Region(x, y, width=3, height=1), spacing=Spacing(right=self.gutter_width), animate=animate, force=True, From 823c348d6c290fc6f6d274554b01f19fbd9e6f29 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 12:36:47 +0000 Subject: [PATCH 081/150] Clamping offsets --- src/textual/document/_wrapped_document.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 27b1e0f05e..01767acad7 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -238,8 +238,8 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: The Location in the document corresponding to the given offset. """ x, y = offset - if x < 0 or y < 0: - raise ValueError("Offset must be non-negative.") + x = max(0, x) + y = max(0, y) if not self._width: # No wrapping, so we directly map offset to location and clamp. From de69905140368f2b02d47c245e4e8a5ea906481e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 13:44:43 +0000 Subject: [PATCH 082/150] Page up and page down --- src/textual/document/_document_navigator.py | 18 +++++++++++++----- src/textual/widgets/_text_area.py | 10 ++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 3da7c2da7f..a7151e8a7c 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -37,7 +37,7 @@ from textual._cells import cell_len from textual.document._document import Location from textual.document._wrapped_document import WrappedDocument -from textual.geometry import clamp +from textual.geometry import Offset, clamp class DocumentNavigator: @@ -296,10 +296,18 @@ def get_location_home(self, location: Location) -> Location: return line_index, first_non_whitespace return line_index, 0 - # TODO - we need to implement methods for going page up and page down - # perhaps we just need a method: given a location and a y-offset return - # the location corresponding to the y-offset applied to the location in the - # *wrapped* document. + def get_location_offset_relative( + self, location: Location, offset_delta: int, tab_width: int + ) -> Location: + # Convert into offset-space to apply the offset. + x_offset, y_offset = self._wrapped_document.location_to_offset( + location, tab_width + ) + # Convert the offset with the delta applied back to location-space. + return self._wrapped_document.offset_to_location( + Offset(x_offset, y_offset + offset_delta), + tab_width, + ) def clamp_reachable(self, location: Location) -> Location: """Given a location, return the nearest location that corresponds to a diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 238f296ddd..f0177c95bf 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1549,8 +1549,9 @@ def action_cursor_page_up(self) -> None: """Move the cursor and scroll up one page.""" height = self.content_size.height _, cursor_location = self.selection - row, column = cursor_location - target = (row - height, column) + target = self._navigator.get_location_offset_relative( + cursor_location, -height, self.indent_width + ) self.scroll_relative(y=-height, animate=False) self.move_cursor(target) @@ -1558,8 +1559,9 @@ def action_cursor_page_down(self) -> None: """Move the cursor and scroll down one page.""" height = self.content_size.height _, cursor_location = self.selection - row, column = cursor_location - target = (row + height, column) + target = self._navigator.get_location_offset_relative( + cursor_location, height, self.indent_width + ) self.scroll_relative(y=height, animate=False) self.move_cursor(target) From 4f6e2bd2c7821a663b6815010aac3eca2dd5acff Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 14:14:47 +0000 Subject: [PATCH 083/150] Fix off by one in gutter width calculation --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f0177c95bf..2a99e0b42c 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1102,7 +1102,7 @@ def gutter_width(self) -> int: # The longest number in the gutter plus two extra characters: `│ `. gutter_margin = 2 gutter_width = ( - len(str(self.document.line_count + 1)) + gutter_margin + len(str(self.document.line_count)) + gutter_margin if self.show_line_numbers else 0 ) From 6c54e325f9c165823f9f31c547ff0155acaf62ac Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 14:36:57 +0000 Subject: [PATCH 084/150] Simplify --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 2a99e0b42c..d0600dbc2c 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -718,7 +718,7 @@ def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: width, _ = self.size available_text_width = ( - width - self.gutter_width - self.styles.scrollbar_size_vertical + width - self.gutter_width - self.scrollbar_size_vertical ) self.wrapped_document.wrap(available_text_width) self._refresh_size() From 8f6b1525a4b295f5f70477a388ce459b877bfca7 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 4 Jan 2024 16:39:23 +0000 Subject: [PATCH 085/150] Rewrapping when gutter width changes --- src/textual/document/_wrapped_document.py | 2 -- src/textual/widgets/_text_area.py | 26 +++++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 01767acad7..194e53f4f2 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -87,8 +87,6 @@ def wrap(self, width: int) -> None: self._offset_to_line_info = offset_to_line_info self._line_index_to_offsets = line_index_to_offsets - - print(f"wrap offsets are now {new_wrap_offsets}") self._wrap_offsets = new_wrap_offsets @property diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index d0600dbc2c..d855c7058c 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -714,15 +714,15 @@ def _on_resize(self) -> None: def _watch_wrap(self) -> None: self._rewrap_and_refresh_virtual_size() + @property + def available_content_width(self) -> int: + width, _ = self.content_size + return width - self.gutter_width + def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: - width, _ = self.size - available_text_width = ( - width - self.gutter_width - self.scrollbar_size_vertical - ) - self.wrapped_document.wrap(available_text_width) + self.wrapped_document.wrap(self.available_content_width) self._refresh_size() - log.debug(f"re-wrapping at width {available_text_width!r}") @property def is_syntax_aware(self) -> bool: @@ -1026,10 +1026,18 @@ def edit(self, edit: Edit) -> EditResult: Data relating to the edit that may be useful. The data returned may be different depending on the edit performed. """ + old_gutter_width = self.gutter_width result = edit.do(self) - self.wrapped_document.wrap_range( - edit.from_location, edit.to_location, result.end_location - ) + print(f"wrap offsets = {self.wrapped_document._wrap_offsets}") + new_gutter_width = self.gutter_width + + if old_gutter_width != new_gutter_width: + self.wrapped_document.wrap(self.available_content_width) + else: + self.wrapped_document.wrap_range( + edit.from_location, edit.to_location, result.end_location + ) + self._refresh_size() edit.after(self) self._build_highlight_map() From c6008505c94e98e519b14cb8e2ae2b8f07c9e627 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 5 Jan 2024 11:06:51 +0000 Subject: [PATCH 086/150] Fixing wrap width to account for scrollbars --- src/textual/widgets/_text_area.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index d855c7058c..6530142f78 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -715,13 +715,15 @@ def _watch_wrap(self) -> None: self._rewrap_and_refresh_virtual_size() @property - def available_content_width(self) -> int: - width, _ = self.content_size - return width - self.gutter_width + def wrap_width(self) -> int: + width, _ = self.scrollable_content_region.size + cursor_width = 1 + return width - self.gutter_width - cursor_width def _rewrap_and_refresh_virtual_size(self) -> None: if self.wrap: - self.wrapped_document.wrap(self.available_content_width) + print(f"wrapping at {self.wrap_width!r}") + self.wrapped_document.wrap(self.wrap_width) self._refresh_size() @property @@ -1032,7 +1034,7 @@ def edit(self, edit: Edit) -> EditResult: new_gutter_width = self.gutter_width if old_gutter_width != new_gutter_width: - self.wrapped_document.wrap(self.available_content_width) + self.wrapped_document.wrap(self.wrap_width) else: self.wrapped_document.wrap_range( edit.from_location, edit.to_location, result.end_location From 1902949956350b375f3db055af959fe5aceb936e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 5 Jan 2024 11:50:46 +0000 Subject: [PATCH 087/150] Ensuring cursor remains visible after toggling wrapping --- src/textual/widgets/_text_area.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 6530142f78..7490260c34 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -412,9 +412,6 @@ def _watch_selection(self, selection: Selection) -> None: # Find the visual offset of the cursor in the document cursor_location = selection.end - self._cursor_offset = self.wrapped_document.location_to_offset( - cursor_location, self.indent_width - ) self.scroll_cursor_visible() cursor_row, cursor_column = cursor_location @@ -435,6 +432,11 @@ def _watch_selection(self, selection: Selection) -> None: self.app.cursor_position = self._cursor_offset self.post_message(self.SelectionChanged(selection, self)) + def _recompute_cursor_offset(self): + self._cursor_offset = self.wrapped_document.location_to_offset( + self.cursor_location, self.indent_width + ) + def find_matching_bracket( self, bracket: str, search_from: Location ) -> Location | None: @@ -507,11 +509,16 @@ def _watch_language(self, language: str | None) -> None: def _watch_show_line_numbers(self) -> None: """The line number gutter contributes to virtual size, so recalculate.""" - self._refresh_size() + self._rewrap_and_refresh_virtual_size() + self.scroll_cursor_visible() def _watch_indent_width(self) -> None: """Changing width of tabs will change document display width.""" - self._refresh_size() + self._rewrap_and_refresh_virtual_size() + self.scroll_cursor_visible() + + def _watch_wrap(self) -> None: + self._rewrap_and_refresh_virtual_size() def _watch_theme(self, theme: str | None) -> None: """We set the styles on this widget when the theme changes, to ensure that @@ -713,18 +720,20 @@ def _on_resize(self) -> None: def _watch_wrap(self) -> None: self._rewrap_and_refresh_virtual_size() + self.call_after_refresh(self.scroll_cursor_visible, center=True) @property def wrap_width(self) -> int: width, _ = self.scrollable_content_region.size cursor_width = 1 - return width - self.gutter_width - cursor_width + if self.wrap: + return width - self.gutter_width - cursor_width + return 0 def _rewrap_and_refresh_virtual_size(self) -> None: - if self.wrap: - print(f"wrapping at {self.wrap_width!r}") - self.wrapped_document.wrap(self.wrap_width) - self._refresh_size() + print(f"wrapping at {self.wrap_width!r}") + self.wrapped_document.wrap(self.wrap_width) + self._refresh_size() @property def is_syntax_aware(self) -> bool: @@ -1225,6 +1234,8 @@ def scroll_cursor_visible( Returns: The offset that was scrolled to bring the cursor into view. """ + self._recompute_cursor_offset() + x, y = self._cursor_offset scroll_offset = self.scroll_to_region( Region(x, y, width=3, height=1), From 2db30832fa9b2e6002de59064340c0e019a70649 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 5 Jan 2024 16:41:22 +0000 Subject: [PATCH 088/150] Fix up on first doc line, down on last doc line --- src/textual/document/_document_navigator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index a7151e8a7c..c54f652ade 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -167,10 +167,6 @@ def get_location_right(self, location: Location) -> Location: def get_location_above(self, location: Location, tab_width: int) -> Location: """Get the location up from the given location in the wrapped document.""" - # Moving up from a position on the first visual line moves us to the start. - if self.is_first_wrapped_line(location): - return 0, 0 - # Get the wrap offsets of the current line. line_index, column_index = location wrap_offsets = self._wrapped_document.get_offsets(line_index) @@ -189,6 +185,9 @@ def get_location_above(self, location: Location, tab_width: int) -> Location: # TODO - account for last_x_offset in both branches here. if section_index == 0: + # Moving up from a position on the first visual line moves us to the start. + if self.is_first_wrapped_line(location): + return 0, 0 # Get the last section from the line above, and find where to move in it. target_row = line_index - 1 target_column = self._wrapped_document.get_target_document_column( @@ -221,9 +220,6 @@ def get_location_below(self, location: Location, tab_width: int) -> Location: line_index, column_index = location document = self._document - if self.is_last_document_line(location): - return line_index, len(document[line_index]) - wrap_offsets = self._wrapped_document.get_offsets(line_index) section_start_columns = [0, *wrap_offsets] section_index = bisect(wrap_offsets, column_index) @@ -234,6 +230,10 @@ def get_location_below(self, location: Location, tab_width: int) -> Location: # If we're at the last section/row of a wrapped line if section_index == len(wrapped_line) - 1: + # Last section of last line: go to end of file. + if self.is_last_document_line(location): + return line_index, len(document[line_index]) + # Go to the first section of the line below. target_row = line_index + 1 target_column = self._wrapped_document.get_target_document_column( From b6d50f8f4f0fba6385135f541d5679fcf68bd00a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 5 Jan 2024 16:47:57 +0000 Subject: [PATCH 089/150] Fix IME positioning in wrapped documents --- src/textual/widgets/_text_area.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 7490260c34..43b9a1f1e0 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -429,10 +429,12 @@ def _watch_selection(self, selection: Selection) -> None: if match_row in range(*self._visible_line_indices): self.refresh_lines(match_row) - self.app.cursor_position = self._cursor_offset + cursor_x, cursor_y = self._cursor_offset + self.app.cursor_position = (cursor_x + self.gutter_width, cursor_y) self.post_message(self.SelectionChanged(selection, self)) def _recompute_cursor_offset(self): + """Recompute the (x, y) coordinate of the cursor in the wrapped document.""" self._cursor_offset = self.wrapped_document.location_to_offset( self.cursor_location, self.indent_width ) From f7a48a5a09cbf2d7dd438c49cbb304b917f615b2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sat, 6 Jan 2024 17:27:34 +0000 Subject: [PATCH 090/150] Add maintain_offsset --- src/textual/document/_document_navigator.py | 6 +++++- src/textual/document/_wrapped_document.py | 3 ++- src/textual/widgets/_text_area.py | 18 +++++++----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index c54f652ade..fa9f1b1f82 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -205,7 +205,9 @@ def get_location_above(self, location: Location, tab_width: int) -> Location: return target_location - def get_location_below(self, location: Location, tab_width: int) -> Location: + def get_location_below( + self, location: Location, tab_width: int, maintain_offset: bool = True + ) -> Location: """Given a location in the raw document, return the raw document location corresponding to moving down in the wrapped representation of the document. @@ -213,6 +215,7 @@ def get_location_below(self, location: Location, tab_width: int) -> Location: Args: location: The location in the raw document. tab_width: The width of the tab stops. + maintain_offset: Maintain the visual x-offset of the cursor. Returns: The location which is *visually* below the given location. @@ -248,6 +251,7 @@ def get_location_below(self, location: Location, tab_width: int) -> Location: ) target_location = line_index, target_column + print(target_column) return target_location def get_location_end(self, location: Location) -> Location: diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 194e53f4f2..acca9f0f31 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -331,7 +331,8 @@ def get_target_document_column( # the original document. target_section = sections[y_offset] - # Add the offsets from the wrapped sections above this one (from the same raw document line) + # Add the offsets from the wrapped sections above this one (from the same raw + # document line) target_section_start = sum( len(wrapped_section) for wrapped_section in sections[:y_offset] ) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 43b9a1f1e0..cdaa397528 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -87,7 +87,6 @@ class TextArea(ScrollView, can_focus=True): TextArea { width: 1fr; height: 1fr; - overflow-y: scroll; } """ @@ -306,15 +305,9 @@ def __init__( self.indent_type: Literal["tabs", "spaces"] = "spaces" """Whether to indent using tabs or spaces.""" - # TODO - this can be removed after integrating the new DocumentNavigator. self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") """Compiled regular expression for what we consider to be a 'word'.""" - self._last_intentional_cell_width: int = 0 - """Tracks the last column (measured in terms of cell length, since we care here about where the cursor - visually moves rather than logical characters) the user explicitly navigated to so that we can reset to it - whenever possible.""" - self._undo_stack: list[Undoable] = [] """A stack (the end of the list is the top of the stack) for tracking edits.""" @@ -839,6 +832,8 @@ def render_line(self, widget_y: int) -> Strip: selection = self.selection start, end = selection + cursor_row, cursor_column = end + selection_top, selection_bottom = sorted(selection) selection_top_row, selection_top_column = selection_top selection_bottom_row, selection_bottom_column = selection_bottom @@ -858,7 +853,6 @@ def render_line(self, widget_y: int) -> Strip: byte_to_codepoint.get(highlight_end) if highlight_end else None, ) - cursor_row, cursor_column = end cursor_line_style = theme.cursor_line_style if theme else None if cursor_line_style and cursor_row == line_index: line.stylize(cursor_line_style) @@ -1609,9 +1603,11 @@ def record_cursor_width(self) -> None: content, then we go down to another row, we want our cursor to jump back to the same offset that we were originally at. """ - row, column = self.selection.end - column_cell_length = self.get_column_width(row, column) - self._last_intentional_cell_width = column_cell_length + cursor_x_offset, _ = self.wrapped_document.location_to_offset( + self.cursor_location, self.indent_width + ) + print(f"recording x offset = {cursor_x_offset}") + self.wrapped_document.last_x_offset = cursor_x_offset # --- Editor operations def insert( From de7c189de88ab940cff94b5e66376cb01ff78a4f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 11:58:34 +0000 Subject: [PATCH 091/150] Snapping back to last horizontal offset where required --- src/textual/document/_document_navigator.py | 15 +++++++++------ src/textual/document/_wrapped_document.py | 5 +++-- src/textual/widgets/_text_area.py | 5 +---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index fa9f1b1f82..c7be2c21fd 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -164,7 +164,9 @@ def get_location_right(self, location: Location) -> Location: target_column = 0 if is_end_of_line else column + 1 return target_row, target_column - def get_location_above(self, location: Location, tab_width: int) -> Location: + def get_location_above( + self, location: Location, tab_width: int, maintain_offset: bool = False + ) -> Location: """Get the location up from the given location in the wrapped document.""" # Get the wrap offsets of the current line. @@ -182,6 +184,7 @@ def get_location_above(self, location: Location, tab_width: int) -> Location: # Convert that cursor offset to a cell (visual) offset current_visual_offset = cell_len(section[:offset_within_section]) + target_offset = max(current_visual_offset, self.last_x_offset) # TODO - account for last_x_offset in both branches here. if section_index == 0: @@ -191,7 +194,7 @@ def get_location_above(self, location: Location, tab_width: int) -> Location: # Get the last section from the line above, and find where to move in it. target_row = line_index - 1 target_column = self._wrapped_document.get_target_document_column( - target_row, current_visual_offset, -1, tab_width + target_row, target_offset, -1, tab_width ) target_location = target_row, target_column else: @@ -199,7 +202,7 @@ def get_location_above(self, location: Location, tab_width: int) -> Location: # Since the section above could be shorter, we need to clamp the column # to a valid value. target_column = self._wrapped_document.get_target_document_column( - line_index, current_visual_offset, section_index - 1, tab_width + line_index, target_offset, section_index - 1, tab_width ) target_location = line_index, target_column @@ -230,6 +233,7 @@ def get_location_below( wrapped_line = self._wrapped_document.get_sections(line_index) section = wrapped_line[section_index] current_visual_offset = cell_len(section[:offset_within_section]) + target_offset = max(current_visual_offset, self.last_x_offset) # If we're at the last section/row of a wrapped line if section_index == len(wrapped_line) - 1: @@ -240,18 +244,17 @@ def get_location_below( # Go to the first section of the line below. target_row = line_index + 1 target_column = self._wrapped_document.get_target_document_column( - target_row, current_visual_offset, 0, tab_width + target_row, target_offset, 0, tab_width ) target_location = target_row, target_column else: # Stay on the same document line, but move forwards to # the location on the section below with the same visual offset. target_column = self._wrapped_document.get_target_document_column( - line_index, current_visual_offset, section_index + 1, tab_width + line_index, target_offset, section_index + 1, tab_width ) target_location = line_index, target_column - print(target_column) return target_location def get_location_end(self, location: Location) -> Location: diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index acca9f0f31..14ed306cfe 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -342,8 +342,9 @@ def get_target_document_column( target_section, x_offset, tab_width ) - # If we're on the final section of a line, the cursor can legally rest beyond the end by a single cell. - # Otherwise, we'll need to ensure that we're keeping the cursor within the bounds of the target section. + # If we're on the final section of a line, the cursor can legally rest beyond + # the end by a single cell. Otherwise, we'll need to ensure that we're + # keeping the cursor within the bounds of the target section. if y_offset != len(sections) - 1 and y_offset != -1: target_column_index = min( target_column_index, target_section_start + len(target_section) - 1 diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index cdaa397528..e9b1e3c786 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -726,7 +726,6 @@ def wrap_width(self) -> int: return 0 def _rewrap_and_refresh_virtual_size(self) -> None: - print(f"wrapping at {self.wrap_width!r}") self.wrapped_document.wrap(self.wrap_width) self._refresh_size() @@ -1035,7 +1034,6 @@ def edit(self, edit: Edit) -> EditResult: """ old_gutter_width = self.gutter_width result = edit.do(self) - print(f"wrap offsets = {self.wrapped_document._wrap_offsets}") new_gutter_width = self.gutter_width if old_gutter_width != new_gutter_width: @@ -1606,8 +1604,7 @@ def record_cursor_width(self) -> None: cursor_x_offset, _ = self.wrapped_document.location_to_offset( self.cursor_location, self.indent_width ) - print(f"recording x offset = {cursor_x_offset}") - self.wrapped_document.last_x_offset = cursor_x_offset + self._navigator.last_x_offset = cursor_x_offset # --- Editor operations def insert( From c9c70ef5b0445bb52c190b9d81eb5324a539cbb9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 12:18:52 +0000 Subject: [PATCH 092/150] Fixing IME positioning when scrolled in wrapped doc --- src/textual/widgets/_text_area.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index e9b1e3c786..f6f0c953c0 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -422,8 +422,7 @@ def _watch_selection(self, selection: Selection) -> None: if match_row in range(*self._visible_line_indices): self.refresh_lines(match_row) - cursor_x, cursor_y = self._cursor_offset - self.app.cursor_position = (cursor_x + self.gutter_width, cursor_y) + self.app.cursor_position = self.cursor_screen_offset self.post_message(self.SelectionChanged(selection, self)) def _recompute_cursor_offset(self): @@ -1138,7 +1137,8 @@ def _on_focus(self, _: events.Focus) -> None: def _toggle_cursor_blink_visible(self) -> None: """Toggle visibility of the cursor for the purposes of 'cursor blink'.""" self._cursor_blink_visible = not self._cursor_blink_visible - cursor_row, _ = self.cursor_location + cursor_row, _ = self._cursor_offset + self.refresh_lines(cursor_row) def _restart_blink(self) -> None: @@ -1345,17 +1345,12 @@ def cursor_location(self, location: Location) -> None: @property def cursor_screen_offset(self) -> Offset: """The offset of the cursor relative to the screen.""" - cursor_row, cursor_column = self.cursor_location + cursor_x, cursor_y = self._cursor_offset scroll_x, scroll_y = self.scroll_offset region_x, region_y, _width, _height = self.content_region - offset_x = ( - region_x - + self.get_column_width(cursor_row, cursor_column) - - scroll_x - + self.gutter_width - ) - offset_y = region_y + cursor_row - scroll_y + offset_x = region_x + cursor_x - scroll_x + self.gutter_width + offset_y = region_y + cursor_y - scroll_y return Offset(offset_x, offset_y) From 1643406d8ffce8273a1d13199ca066451ef555ce Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 13:19:19 +0000 Subject: [PATCH 093/150] Fix cursor not blinking in wrapped document --- src/textual/widgets/_text_area.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f6f0c953c0..fe5844d3a3 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1137,9 +1137,8 @@ def _on_focus(self, _: events.Focus) -> None: def _toggle_cursor_blink_visible(self) -> None: """Toggle visibility of the cursor for the purposes of 'cursor blink'.""" self._cursor_blink_visible = not self._cursor_blink_visible - cursor_row, _ = self._cursor_offset - - self.refresh_lines(cursor_row) + _, cursor_y = self._cursor_offset + self.refresh_lines(cursor_y) def _restart_blink(self) -> None: """Reset the cursor blink timer.""" From 24d419f694f4c5cfa7b7dfea005994a30963c0ba Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 15:32:29 +0000 Subject: [PATCH 094/150] Parameterise "smart home key" --- src/textual/document/_document_navigator.py | 21 ++++++++++++--------- src/textual/widgets/_text_area.py | 13 ++++++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index c7be2c21fd..912bb8685c 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -281,7 +281,9 @@ def get_location_end(self, location: Location) -> Location: target_column = len(self._document[line_index]) return line_index, target_column - def get_location_home(self, location: Location) -> Location: + def get_location_home( + self, location: Location, smart_home: bool = False + ) -> Location: """Get the location corresponding to the start of the current section.""" line_index, column_offset = location wrap_offsets = self._wrapped_document.get_offsets(line_index) @@ -293,14 +295,15 @@ def get_location_home(self, location: Location) -> Location: else: # No wrapping to consider, go to the start of the document line line = self._wrapped_document.document[line_index] - first_non_whitespace = 0 - for index, code_point in enumerate(line): - if not code_point.isspace(): - first_non_whitespace = index - break - - if column_offset <= first_non_whitespace and column_offset != 0: - return line_index, first_non_whitespace + target_column = 0 + if smart_home: + for index, code_point in enumerate(line): + if not code_point.isspace(): + target_column = index + break + + if column_offset <= target_column and column_offset != 0: + return line_index, target_column return line_index, 0 def get_location_offset_relative( diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index fe5844d3a3..1df0c490ce 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1479,16 +1479,23 @@ def get_cursor_line_end_location(self) -> Location: def action_cursor_line_start(self, select: bool = False) -> None: """Move the cursor to the start of the line.""" - target = self.get_cursor_line_start_location() + target = self.get_cursor_line_start_location(smart_home=True) self.move_cursor(target, select=select) - def get_cursor_line_start_location(self) -> Location: + def get_cursor_line_start_location(self, smart_home: bool = False) -> Location: """Get the location of the start of the current line. + Args: + smart_home: If True, use "smart home key" behavior - go to the first + non-whitespace character on the line, and if already there, go to + offset 0. Smart home only works when wrapping is disabled. + Returns: The (row, column) location of the start of the cursors current line. """ - return self._navigator.get_location_home(self.cursor_location) + return self._navigator.get_location_home( + self.cursor_location, smart_home=smart_home + ) def action_cursor_word_left(self, select: bool = False) -> None: """Move the cursor left by a single word, skipping trailing whitespace. From b8fbb1ef12d1c7727d4232b88e5016eee32af60d Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 15:40:38 +0000 Subject: [PATCH 095/150] Fixing delete to start of line --- src/textual/document/_document_navigator.py | 2 +- src/textual/widgets/_text_area.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 912bb8685c..b58363ba11 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -186,7 +186,6 @@ def get_location_above( current_visual_offset = cell_len(section[:offset_within_section]) target_offset = max(current_visual_offset, self.last_x_offset) - # TODO - account for last_x_offset in both branches here. if section_index == 0: # Moving up from a position on the first visual line moves us to the start. if self.is_first_wrapped_line(location): @@ -304,6 +303,7 @@ def get_location_home( if column_offset <= target_column and column_offset != 0: return line_index, target_column + return line_index, 0 def get_location_offset_relative( diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 1df0c490ce..f763ce332d 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1732,8 +1732,7 @@ def action_delete_line(self) -> None: def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" from_location = self.selection.end - cursor_row, cursor_column = from_location - to_location = (cursor_row, 0) + to_location = self.get_cursor_line_start_location() self.delete(from_location, to_location, maintain_selection_offset=False) def action_delete_to_end_of_line(self) -> None: From ac9b88ec108d5c70a186d742efa20c038cd20df6 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 8 Jan 2024 15:41:28 +0000 Subject: [PATCH 096/150] Fix deleting to end of line --- src/textual/widgets/_text_area.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f763ce332d..a13073e0b7 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1738,8 +1738,7 @@ def action_delete_to_start_of_line(self) -> None: def action_delete_to_end_of_line(self) -> None: """Deletes from the cursor location to the end of the line.""" from_location = self.selection.end - cursor_row, cursor_column = from_location - to_location = (cursor_row, len(self.document[cursor_row])) + to_location = self.get_cursor_line_end_location() self.delete(from_location, to_location, maintain_selection_offset=False) def action_delete_word_left(self) -> None: From 146c1526697ee6ff3439a85d1bbe2667b39b61e1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 09:59:56 +0000 Subject: [PATCH 097/150] Configurable indent behaviour --- src/textual/widgets/_text_area.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index a13073e0b7..8e1fa2b9fd 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -277,6 +277,7 @@ def __init__( language: str | None = None, theme: str | None = None, wrap: bool = False, + tab_behaviour: Literal["focus", "indent"] = "indent", name: str | None = None, id: str | None = None, classes: str | None = None, @@ -288,6 +289,7 @@ def __init__( text: The initial text to load into the TextArea. language: The language to use. theme: The theme to use. + tab_behaviour: If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab. name: The name of the `TextArea` widget. id: The ID of the widget, used to refer to it from Textual CSS. classes: One or more Textual CSS compatible class names separated by spaces. @@ -350,6 +352,8 @@ def __init__( self._reactive_wrap = wrap + self.tab_behaviour = tab_behaviour + @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. @@ -1052,9 +1056,11 @@ async def _on_key(self, event: events.Key) -> None: """Handle key presses which correspond to document inserts.""" key = event.key insert_values = { - "tab": " " * self._find_columns_to_next_tab_stop(), "enter": "\n", } + if self.tab_behaviour == "indent": + insert_values["tab"] = " " * self._find_columns_to_next_tab_stop() + self._restart_blink() if event.is_printable or key in insert_values: event.stop() From d0b01b1643a970dfd8c99b57b600297d8afe3e70 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 11:04:26 +0000 Subject: [PATCH 098/150] Maintain offset when deleting a line --- src/textual/widgets/_text_area.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8e1fa2b9fd..414b43e3c0 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1734,6 +1734,7 @@ def action_delete_line(self) -> None: to_location = (end_row + 1, 0) self.delete(from_location, to_location, maintain_selection_offset=False) + self.move_cursor_relative(0, end_column) def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" From b49781a478b1eea6e45922d1be7115bff8783982 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 11:05:44 +0000 Subject: [PATCH 099/150] Maintain offset when deleting a line --- src/textual/widgets/_text_area.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 414b43e3c0..08e18c02b9 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1731,10 +1731,9 @@ def action_delete_line(self) -> None: end_row -= 1 from_location = (start_row, 0) - to_location = (end_row + 1, 0) + to_location = (end_row + 1, end_column) self.delete(from_location, to_location, maintain_selection_offset=False) - self.move_cursor_relative(0, end_column) def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" From 9fe7f632627c8d6aef50f228a88c98fddc268c13 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 11:41:44 +0000 Subject: [PATCH 100/150] Maintain cursor width --- src/textual/widgets/_text_area.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 08e18c02b9..8d3caa6fd5 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1734,6 +1734,7 @@ def action_delete_line(self) -> None: to_location = (end_row + 1, end_column) self.delete(from_location, to_location, maintain_selection_offset=False) + self.move_cursor_relative(columns=end_column, record_width=False) def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" From 264af63353684b5f14b0d53f259ddb2c238b43ef Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 16:52:01 +0000 Subject: [PATCH 101/150] Correctly setting line widths --- src/textual/document/_wrapped_document.py | 5 +---- src/textual/widgets/_text_area.py | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 14ed306cfe..1805c7580b 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -51,9 +51,6 @@ def __init__( self._line_index_to_offsets: list[list[VerticalOffset]] = [] """Maps line indices to all the vertical offsets which correspond to that line.""" - # self._offset_to_section_offset: dict[int, int] = {} - # """Maps y_offsets to the offsets of the section within the line.""" - self._width: int = width """The width the document is currently wrapped at. This will correspond with the value last passed into the `wrap` method.""" @@ -354,7 +351,7 @@ def get_target_document_column( def get_sections(self, line_index: int) -> list[str]: line_offsets = self._wrap_offsets[line_index] - wrapped_lines = Text(self.document[line_index]).divide(line_offsets) + wrapped_lines = Text(self.document[line_index], end="").divide(line_offsets) return [line.plain for line in wrapped_lines] def get_offsets(self, line_index: int) -> list[int]: diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8d3caa6fd5..8ce1ebd89f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -829,8 +829,8 @@ def render_line(self, widget_y: int) -> Strip: line_character_count = len(line) line.tab_size = self.indent_width + line.set_length(line_character_count + 1) # space at end for cursor virtual_width, virtual_height = self.virtual_size - line.set_length(line.cell_len + 1) # Make space for cursor at end. selection = self.selection start, end = selection @@ -953,6 +953,7 @@ def render_line(self, widget_y: int) -> Strip: if wrap_offsets: sections = line.divide(wrap_offsets) # TODO cache result with edit count line = sections[section_offset] + line.end = "" if self.wrap: # If we're wrapping, the line should be wrapped to the width available for the text. @@ -963,8 +964,6 @@ def render_line(self, widget_y: int) -> Strip: target_width = virtual_width - self.gutter_width # Set the width of this section to the target width. - line.set_length(target_width) - console = self.app.console gutter_segments = console.render(gutter) text_segments = console.render(line, console.options.update_width(target_width)) @@ -980,10 +979,11 @@ def render_line(self, widget_y: int) -> Strip: # Stylize the line the cursor is currently on. if cursor_row == line_index: - text_strip = text_strip.apply_style(cursor_line_style) + line_style = cursor_line_style else: - text_strip = text_strip.apply_style(theme.base_style if theme else None) + line_style = theme.base_style if theme else None + text_strip = text_strip.extend_cell_length(target_width, line_style) strip = Strip.join([gutter_strip, text_strip]).simplify() return strip.apply_style( From e000298e7766d0f0719cbf8b49a676eca8da583e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 9 Jan 2024 16:54:42 +0000 Subject: [PATCH 102/150] Fixing rendering when wrapping disabled --- src/textual/widgets/_text_area.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8ce1ebd89f..8cf0f29005 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -955,14 +955,7 @@ def render_line(self, widget_y: int) -> Strip: line = sections[section_offset] line.end = "" - if self.wrap: - # If we're wrapping, the line should be wrapped to the width available for the text. - # That is the width of the widget minus the gutter width and scrollbar width. - target_width = self.size.width - self.gutter_width - else: - # If we're not wrapping, then we want to expand the line such that it's equal to the virtual width. - target_width = virtual_width - self.gutter_width - + target_width = self.size.width - self.gutter_width # Set the width of this section to the target width. console = self.app.console gutter_segments = console.render(gutter) From 6f41dc281da6da98990ff9a937998c48b8fdedb5 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 10 Jan 2024 10:49:46 +0000 Subject: [PATCH 103/150] Remove some debugging --- src/textual/widgets/_text_area.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8cf0f29005..48881e57fb 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -955,8 +955,12 @@ def render_line(self, widget_y: int) -> Strip: line = sections[section_offset] line.end = "" - target_width = self.size.width - self.gutter_width - # Set the width of this section to the target width. + base_width = ( + self.scrollable_content_region.size.width + if self.wrap + else max(virtual_width, self.region.size.width) + ) + target_width = base_width - self.gutter_width console = self.app.console gutter_segments = console.render(gutter) text_segments = console.render(line, console.options.update_width(target_width)) From 9fb774c431b1094db3bd2eab383a73f33dcc323b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 10 Jan 2024 11:04:34 +0000 Subject: [PATCH 104/150] Fix issue when crop start was == cell length of a Strip --- src/textual/strip.py | 2 +- tests/test_strip.py | 76 ++++++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/textual/strip.py b/src/textual/strip.py index 7bad6e6c3e..bdc8a0595e 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -385,7 +385,7 @@ def crop(self, start: int, end: int | None = None) -> Strip: add_segment = output_segments.append iter_segments = iter(self._segments) segment: Segment | None = None - if start > self.cell_length: + if start >= self.cell_length: strip = Strip([], 0) else: for segment in iter_segments: diff --git a/tests/test_strip.py b/tests/test_strip.py index fda2057a89..7f6d9bae63 100644 --- a/tests/test_strip.py +++ b/tests/test_strip.py @@ -65,23 +65,22 @@ def test_eq(): def test_adjust_cell_length(): - for repeat in range(3): - assert Strip([]).adjust_cell_length(3) == Strip([Segment(" ")]) - assert Strip([Segment("f")]).adjust_cell_length(3) == Strip( - [Segment("f"), Segment(" ")] - ) - assert Strip([Segment("💩")]).adjust_cell_length(3) == Strip( - [Segment("💩"), Segment(" ")] - ) - - assert Strip([Segment("💩💩")]).adjust_cell_length(3) == Strip([Segment("💩 ")]) - assert Strip([Segment("💩💩")]).adjust_cell_length(4) == Strip([Segment("💩💩")]) - assert Strip([Segment("💩"), Segment("💩💩")]).adjust_cell_length(2) == Strip( - [Segment("💩")] - ) - assert Strip([Segment("💩"), Segment("💩💩")]).adjust_cell_length(4) == Strip( - [Segment("💩"), Segment("💩")] - ) + assert Strip([]).adjust_cell_length(3) == Strip([Segment(" ")]) + assert Strip([Segment("f")]).adjust_cell_length(3) == Strip( + [Segment("f"), Segment(" ")] + ) + assert Strip([Segment("💩")]).adjust_cell_length(3) == Strip( + [Segment("💩"), Segment(" ")] + ) + + assert Strip([Segment("💩💩")]).adjust_cell_length(3) == Strip([Segment("💩 ")]) + assert Strip([Segment("💩💩")]).adjust_cell_length(4) == Strip([Segment("💩💩")]) + assert Strip([Segment("💩"), Segment("💩💩")]).adjust_cell_length(2) == Strip( + [Segment("💩")] + ) + assert Strip([Segment("💩"), Segment("💩💩")]).adjust_cell_length(4) == Strip( + [Segment("💩"), Segment("💩")] + ) def test_extend_cell_length(): @@ -101,8 +100,6 @@ def test_simplify(): def test_apply_filter(): strip = Strip([Segment("foo", Style.parse("red"))]) expected = Strip([Segment("foo", Style.parse("#1b1b1b"))]) - print(repr(strip)) - print(repr(expected)) assert strip.apply_filter(Monochrome(), Color(0, 0, 0)) == expected @@ -128,26 +125,37 @@ def test_style_links(): def test_crop(): - for repeat in range(3): - assert Strip([Segment("foo")]).crop(0, 3) == Strip([Segment("foo")]) - assert Strip([Segment("foo")]).crop(0, 2) == Strip([Segment("fo")]) - assert Strip([Segment("foo")]).crop(0, 1) == Strip([Segment("f")]) + assert Strip([Segment("foo")]).crop(0, 3) == Strip([Segment("foo")]) + assert Strip([Segment("foo")]).crop(0, 2) == Strip([Segment("fo")]) + assert Strip([Segment("foo")]).crop(0, 1) == Strip([Segment("f")]) - assert Strip([Segment("foo")]).crop(1, 3) == Strip([Segment("oo")]) - assert Strip([Segment("foo")]).crop(1, 2) == Strip([Segment("o")]) - assert Strip([Segment("foo")]).crop(1, 1) == Strip([Segment("")]) + assert Strip([Segment("foo")]).crop(1, 3) == Strip([Segment("oo")]) + assert Strip([Segment("foo")]).crop(1, 2) == Strip([Segment("o")]) + assert Strip([Segment("foo")]).crop(1, 1) == Strip([Segment("")]) - assert Strip([Segment("foo💩"), Segment("b💩ar"), Segment("ba💩z")]).crop( - 1, 6 - ) == Strip([Segment("oo💩"), Segment("b")]) + assert Strip([Segment("foo💩"), Segment("b💩ar"), Segment("ba💩z")]).crop( + 1, 6 + ) == Strip([Segment("oo💩"), Segment("b")]) + + +@pytest.mark.parametrize( + "text,crop,output", + [ + ["foo", (0, 5), [Segment("foo")]], + ["foo", (2, 5), [Segment("o")]], + ["foo", (3, 5), []], + ["foo", (4, 6), []], + ], +) +def test_crop_out_of_bounds(text, crop, output): + assert Strip([Segment(text)]).crop(*crop) == Strip(output) def test_divide(): - for repeat in range(3): - assert Strip([Segment("foo")]).divide([1, 2]) == [ - Strip([Segment("f")]), - Strip([Segment("o")]), - ] + assert Strip([Segment("foo")]).divide([1, 2]) == [ + Strip([Segment("f")]), + Strip([Segment("o")]), + ] @pytest.mark.parametrize( From 17448d1e45049d48ea2ba022961f830947273b23 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 10 Jan 2024 11:13:05 +0000 Subject: [PATCH 105/150] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba75ae4974..b5c89fa5ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Parameter `animate` from `DataTable.move_cursor` was being ignored https://github.com/Textualize/textual/issues/3840 - Fixed a crash if `DirectoryTree.show_root` was set before the DOM was fully available https://github.com/Textualize/textual/issues/2363 -- Live reloading of TCSS wouldn't apply CSS changes to screens under the top screen of the stack https://github.com/Textualize/textual/issues/3931 +- Live reloading of TCSS wouldn't apply CSS changes to screens under the top screen of the stack https://github.com/Textualize/textual/issues/3931\ +- Fix issue with `Strip.crop` when crop window start aligned with strip end https://github.com/Textualize/textual/pull/3998 ## [0.47.1] - 2023-01-05 From 5843e91b1bd4e8aad30550dcd8f70aba8129d491 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 11 Jan 2024 17:04:22 +0000 Subject: [PATCH 106/150] Begin accounting for tab widths in wrap offset computation --- Makefile | 2 +- src/textual/_wrap.py | 110 ++++++++++++++++++++++ src/textual/document/_wrapped_document.py | 21 ++++- src/textual/widgets/_text_area.py | 17 ++-- 4 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 src/textual/_wrap.py diff --git a/Makefile b/Makefile index 8b5f8d2191..0aa62facab 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ run := poetry run .PHONY: test test: - $(run) pytest --cov-report term-missing --cov=textual tests/ -vv + $(run) pytest --cov-report term-missing --cov=textual tests/ -vv .PHONY: unit-test unit-test: diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py new file mode 100644 index 0000000000..24bdd76d97 --- /dev/null +++ b/src/textual/_wrap.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +import re +from typing import Iterable + +from rich.cells import chop_cells + +from ._cells import cell_len +from ._loop import loop_last + +re_chunk = re.compile(r"\s*\S+\s*") + + +def chunks(text: str) -> Iterable[tuple[int, int, str]]: + """Yields each "chunk" from the text as a tuple containing (start_index, end_index, chunk_content). + A "chunk" in this context refers to a word and any whitespace around it. + + Args: + text: The text to split into chunks. + + Returns: + Yields tuples containing the start, end and content for each chunk. + """ + position = 0 + chunk_match = re_chunk.match(text, position) + while chunk_match is not None: + start, end = chunk_match.span() + chunk = chunk_match.group(0) + yield start, end, chunk + chunk_match = re_chunk.match(text, end) + + +def divide_line( + text: str, + width: int, + tab_size: int, + fold: bool = True, + keep_whitespace: bool = False, +) -> list[int]: + """Given a string of text, and a width (measured in cells), return a list + of codepoint indices which the string should be split at in order for it to fit + within the given width. + + Args: + text: The text to examine. + width: The available cell width. + tab_size: The tab stop width. + fold: If True, words longer than `width` will be folded onto a new line. + keep_whitespace: If True, consecutive spaces will not be collapsed at line ends. + + Returns: + A list of indices to break the line at. + """ + break_positions: list[int] = [] # offsets to insert the breaks at + append = break_positions.append + cell_offset = 0 + _cell_len = cell_len + + # todo! could we offset the wrap offsets after computing them, by going through them, and summing the widths of + # all tab characters that appeared before the wrap offset on the line? for example if we have a wrap offset of + # 14, then we go through the tab_widths tuples until we’ve seen 14 codepoints, and note the total widths of tabs + # encountered. + + # these wrap offsets all assume every tab has width 1. + # now, for each offset + + for start, _end, chunk in chunks(text): + word_width = _cell_len( + chunk + ) # todo, 1st, terrible name, 2nd can we get the "word width" here to account for tab widths? + if keep_whitespace: + chunk_width = word_width + width_contribution = chunk_width + else: + chunk_width = _cell_len(chunk.rstrip()) + width_contribution = word_width + + remaining_space = width - cell_offset + chunk_fits = remaining_space >= chunk_width + + if chunk_fits: + # Simplest case - the word fits within the remaining width for this line. + cell_offset += width_contribution + else: + # Not enough space remaining for this word on the current line. + if chunk_width > width: + # The word doesn't fit on any line, so we can't simply + # place it on the next line... + if fold: + # Fold the word across multiple lines. + folded_word = chop_cells(chunk, width=width) + for last, line in loop_last(folded_word): + if start: + append(start) + if last: + cell_offset = _cell_len(line) + else: + start += len(line) + else: + # Folding isn't allowed, so crop the word. + if start: + append(start) + cell_offset = width_contribution + elif cell_offset and start: + # The word doesn't fit within the remaining space on the current + # line, but it *can* fit on to the next (empty) line. + append(start) + cell_offset = width_contribution + + return break_positions diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 1805c7580b..7f245625f3 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -9,10 +9,10 @@ from bisect import bisect_right -from rich._wrap import divide_line from rich.text import Text from textual._cells import cell_len, cell_width_to_column_index +from textual._wrap import divide_line from textual.document._document import DocumentBase, Location from textual.expand_tabs import expand_tabs_inline from textual.geometry import Offset, clamp @@ -27,6 +27,7 @@ def __init__( self, document: DocumentBase, width: int = 0, + tab_width: int = 4, ) -> None: """Construct a WrappedDocument. @@ -36,6 +37,7 @@ def __init__( Args: document: The document to wrap. width: The width to wrap at. + tab_width: The maximum width to consider for tab characters. """ self.document = document """The document wrapping is performed on.""" @@ -55,13 +57,14 @@ def __init__( """The width the document is currently wrapped at. This will correspond with the value last passed into the `wrap` method.""" - self.wrap(width) + self.wrap(width, tab_width) - def wrap(self, width: int) -> None: + def wrap(self, width: int, tab_width: int) -> None: """Wrap and cache all lines in the document. Args: width: The width to wrap at. 0 for no wrapping. + tab_width: The maximum width to consider for tab characters. """ self._width = width @@ -74,7 +77,10 @@ def wrap(self, width: int) -> None: current_offset = 0 for line_index, line in enumerate(self.document.lines): - wrap_offsets = divide_line(line, width) if width else [] + line = expand_tabs_inline(line, tab_width) + wrap_offsets = ( + divide_line(line, width, keep_whitespace=True) if width else [] + ) append_wrap_offset(wrap_offsets) line_index_to_offsets.append([]) for section_y_offset in range(len(wrap_offsets) + 1): @@ -115,6 +121,7 @@ def wrap_range( start: Location, old_end: Location, new_end: Location, + tab_width: int, ) -> None: """Incrementally recompute wrapping based on a performed edit. @@ -124,6 +131,7 @@ def wrap_range( start: The start location of the edit that was performed in document-space. old_end: The old end location of the edit in document-space. new_end: The new end location of the edit in document-space. + tab_width: The maximum width to consider for tab characters. """ # Get all the text on the lines between start and end in document space @@ -165,7 +173,10 @@ def wrap_range( # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset for line_index, line in enumerate(new_lines, top_line_index): - wrap_offsets = divide_line(line, width) if width else [] + line = expand_tabs_inline(line, tab_width) + wrap_offsets = ( + divide_line(line, width, keep_whitespace=True) if width else [] + ) append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 48881e57fb..86aa02763f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -327,13 +327,13 @@ def __init__( self._highlight_query: "Query" | None = None """The query that's currently being used for highlighting.""" - self.document: DocumentBase + self.document: DocumentBase = Document(text) """The document this widget is currently editing.""" - self.wrapped_document: WrappedDocument + self.wrapped_document: WrappedDocument = WrappedDocument(self.document) """The wrapped view of the document.""" - self._navigator: DocumentNavigator + self._navigator: DocumentNavigator = DocumentNavigator(self.wrapped_document) """Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.""" @@ -681,7 +681,7 @@ def _set_document(self, text: str, language: str | None) -> None: document = Document(text) self.document = document - self.wrapped_document = WrappedDocument(document) + self.wrapped_document = WrappedDocument(document, tab_width=self.indent_width) self._navigator = DocumentNavigator(self.wrapped_document) self._build_highlight_map() self.move_cursor((0, 0)) @@ -729,7 +729,7 @@ def wrap_width(self) -> int: return 0 def _rewrap_and_refresh_virtual_size(self) -> None: - self.wrapped_document.wrap(self.wrap_width) + self.wrapped_document.wrap(self.wrap_width, tab_width=self.indent_width) self._refresh_size() @property @@ -1037,10 +1037,13 @@ def edit(self, edit: Edit) -> EditResult: new_gutter_width = self.gutter_width if old_gutter_width != new_gutter_width: - self.wrapped_document.wrap(self.wrap_width) + self.wrapped_document.wrap(self.wrap_width, self.indent_width) else: self.wrapped_document.wrap_range( - edit.from_location, edit.to_location, result.end_location + edit.from_location, + edit.to_location, + result.end_location, + self.indent_width, ) self._refresh_size() From 9912b764fa89bf4b84fb237289dd81a3dbeaa029 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 11 Jan 2024 17:05:02 +0000 Subject: [PATCH 107/150] Formatting --- src/textual/_wrap.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 24bdd76d97..49c9fa8b0f 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -65,9 +65,8 @@ def divide_line( # now, for each offset for start, _end, chunk in chunks(text): - word_width = _cell_len( - chunk - ) # todo, 1st, terrible name, 2nd can we get the "word width" here to account for tab widths? + # todo, 1st, terrible name, 2nd can we get the "word width" here to account for tab widths? + word_width = _cell_len(chunk) if keep_whitespace: chunk_width = word_width width_contribution = chunk_width From 7852a1e88d418a46330125c41a45c392d0fd889d Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 11 Jan 2024 17:21:22 +0000 Subject: [PATCH 108/150] Notes --- src/textual/_wrap.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 49c9fa8b0f..1a62604a42 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -35,7 +35,6 @@ def divide_line( width: int, tab_size: int, fold: bool = True, - keep_whitespace: bool = False, ) -> list[int]: """Given a string of text, and a width (measured in cells), return a list of codepoint indices which the string should be split at in order for it to fit @@ -46,7 +45,6 @@ def divide_line( width: The available cell width. tab_size: The tab stop width. fold: If True, words longer than `width` will be folded onto a new line. - keep_whitespace: If True, consecutive spaces will not be collapsed at line ends. Returns: A list of indices to break the line at. @@ -64,15 +62,18 @@ def divide_line( # these wrap offsets all assume every tab has width 1. # now, for each offset + tab_widths = get_tab_widths(text, tab_size) + for start, _end, chunk in chunks(text): # todo, 1st, terrible name, 2nd can we get the "word width" here to account for tab widths? - word_width = _cell_len(chunk) - if keep_whitespace: - chunk_width = word_width - width_contribution = chunk_width - else: - chunk_width = _cell_len(chunk.rstrip()) - width_contribution = word_width + chunk_width = _cell_len(chunk) + # Count the tabs + # Determine the total widths of the tabs in this chunk + # Account for the tabs such that we convert every tab + # character from width 1 to the new width by adding tab_width + # MINUS one for each tab. + # So old width + total tab width - num tabs in chunk + num_tabs = chunk.count(tab) remaining_space = width - cell_offset chunk_fits = remaining_space >= chunk_width @@ -99,11 +100,11 @@ def divide_line( # Folding isn't allowed, so crop the word. if start: append(start) - cell_offset = width_contribution + cell_offset = chunk_width elif cell_offset and start: # The word doesn't fit within the remaining space on the current # line, but it *can* fit on to the next (empty) line. append(start) - cell_offset = width_contribution + cell_offset = chunk_width return break_positions From e626f8d950d337120c0223631b92e541f54903c1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 15 Jan 2024 13:30:44 +0000 Subject: [PATCH 109/150] Remove duplicate method --- src/textual/widgets/_text_area.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 86aa02763f..59a24322f6 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -515,9 +515,6 @@ def _watch_indent_width(self) -> None: self._rewrap_and_refresh_virtual_size() self.scroll_cursor_visible() - def _watch_wrap(self) -> None: - self._rewrap_and_refresh_virtual_size() - def _watch_theme(self, theme: str | None) -> None: """We set the styles on this widget when the theme changes, to ensure that if padding is applied, the colours match.""" From 0da49c9dec1787e5f2b86145825c5176a006577f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 10:24:34 +0000 Subject: [PATCH 110/150] Fixing insert value on tab key press, tests for get_tab_widths --- src/textual/_wrap.py | 40 +++++++++++++---------- src/textual/document/_wrapped_document.py | 10 ++---- src/textual/widgets/_text_area.py | 5 ++- tests/test_expand_tabs.py | 9 ++++- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 1a62604a42..ccf93a7b15 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -7,6 +7,7 @@ from ._cells import cell_len from ._loop import loop_last +from .expand_tabs import get_tab_widths re_chunk = re.compile(r"\s*\S+\s*") @@ -21,13 +22,11 @@ def chunks(text: str) -> Iterable[tuple[int, int, str]]: Returns: Yields tuples containing the start, end and content for each chunk. """ - position = 0 - chunk_match = re_chunk.match(text, position) - while chunk_match is not None: + end = 0 + while (chunk_match := re_chunk.match(text, end)) is not None: start, end = chunk_match.span() chunk = chunk_match.group(0) yield start, end, chunk - chunk_match = re_chunk.match(text, end) def divide_line( @@ -59,28 +58,33 @@ def divide_line( # 14, then we go through the tab_widths tuples until we’ve seen 14 codepoints, and note the total widths of tabs # encountered. - # these wrap offsets all assume every tab has width 1. - # now, for each offset + # build mapping of tab positions to tab widths. then, for each chunk, check if a + # get all of the tab - tab_widths = get_tab_widths(text, tab_size) + # foo\tbar\t\baz - for start, _end, chunk in chunks(text): - # todo, 1st, terrible name, 2nd can we get the "word width" here to account for tab widths? + tab_widths = get_tab_widths(text, tab_size) + cumulative_widths = [] + cumulative_width = 0 + for tab_section, tab_width in tab_widths: + cumulative_widths.extend([cumulative_width] * len(tab_section)) + cumulative_width += tab_width + + for start, end, chunk in chunks(text): + # todo, 1st, terrible name, 2nd can we get the "word width" here to account + # for tab widths? chunk_width = _cell_len(chunk) - # Count the tabs - # Determine the total widths of the tabs in this chunk - # Account for the tabs such that we convert every tab - # character from width 1 to the new width by adding tab_width - # MINUS one for each tab. - # So old width + total tab width - num tabs in chunk - num_tabs = chunk.count(tab) - + print(start, end, chunk, len(chunk)) + tab_width_before_start = cumulative_widths[start] + tab_width_before_end = cumulative_widths[end] + chunk_tab_width = tab_width_before_end - tab_width_before_start + chunk_width += chunk_tab_width remaining_space = width - cell_offset chunk_fits = remaining_space >= chunk_width if chunk_fits: # Simplest case - the word fits within the remaining width for this line. - cell_offset += width_contribution + cell_offset += chunk_width else: # Not enough space remaining for this word on the current line. if chunk_width > width: diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 7f245625f3..d6f6a3c9f0 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -77,10 +77,7 @@ def wrap(self, width: int, tab_width: int) -> None: current_offset = 0 for line_index, line in enumerate(self.document.lines): - line = expand_tabs_inline(line, tab_width) - wrap_offsets = ( - divide_line(line, width, keep_whitespace=True) if width else [] - ) + wrap_offsets = divide_line(line, width, tab_size=tab_width) if width else [] append_wrap_offset(wrap_offsets) line_index_to_offsets.append([]) for section_y_offset in range(len(wrap_offsets) + 1): @@ -173,10 +170,7 @@ def wrap_range( # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset for line_index, line in enumerate(new_lines, top_line_index): - line = expand_tabs_inline(line, tab_width) - wrap_offsets = ( - divide_line(line, width, keep_whitespace=True) if width else [] - ) + wrap_offsets = divide_line(line, width, tab_size=tab_width) if width else [] append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 59a24322f6..cd0dae9b94 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1056,7 +1056,10 @@ async def _on_key(self, event: events.Key) -> None: "enter": "\n", } if self.tab_behaviour == "indent": - insert_values["tab"] = " " * self._find_columns_to_next_tab_stop() + if self.indent_type == "tabs": + insert_values["tab"] = "\t" + else: + insert_values["tab"] = " " * self._find_columns_to_next_tab_stop() self._restart_blink() if event.is_printable or key in insert_values: diff --git a/tests/test_expand_tabs.py b/tests/test_expand_tabs.py index d1d31fb110..b120978da5 100644 --- a/tests/test_expand_tabs.py +++ b/tests/test_expand_tabs.py @@ -1,6 +1,6 @@ import pytest -from textual.expand_tabs import expand_tabs_inline +from textual.expand_tabs import expand_tabs_inline, get_tab_widths @pytest.mark.parametrize( @@ -23,3 +23,10 @@ ) def test_expand_tabs_inline(line, expanded_line): assert expand_tabs_inline(line) == expanded_line + + +def test_get_tab_widths(): + assert get_tab_widths("\tbar") == [("", 4), ("bar", 0)] + assert get_tab_widths("\tbar\t") == [("", 4), ("bar", 1)] + assert get_tab_widths("\tfoo\t\t") == [("", 4), ("foo", 1), ("", 4)] + assert get_tab_widths("\t木foo\t木\t\t") == [("", 4), ("木foo", 3), ("木", 2), ("", 4)] From ee9d1a89c769bd60f3fa1a6e51a5e4420178318c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 10:40:45 +0000 Subject: [PATCH 111/150] Add tests for chunks --- tests/test_wrap.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_wrap.py diff --git a/tests/test_wrap.py b/tests/test_wrap.py new file mode 100644 index 0000000000..b60892ccae --- /dev/null +++ b/tests/test_wrap.py @@ -0,0 +1,15 @@ +from textual._wrap import chunks + + +def test_chunks(): + assert list(chunks("")) == [] + assert list(chunks(" ")) == [] + assert list(chunks("\t")) == [] + assert list(chunks("foo")) == [(0, 3, "foo")] + assert list(chunks(" foo ")) == [(0, 7, " foo ")] + assert list(chunks("foo bar")) == [(0, 4, "foo "), (4, 7, "bar")] + assert list(chunks("\tfoo bar")) == [(0, 5, "\tfoo "), (5, 8, "bar")] + assert list(chunks(" foo bar")) == [(0, 5, " foo "), (5, 8, "bar")] + assert list(chunks("foo bar ")) == [(0, 4, "foo "), (4, 10, "bar ")] + assert list(chunks("foo\t bar ")) == [(0, 6, "foo\t "), (6, 12, "bar ")] + assert list(chunks("木\t 川 ")) == [(0, 4, "木\t "), (4, 8, "川 ")] From 43f86979397f7ee4207c4493c6bcbeec7090f018 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 10:49:26 +0000 Subject: [PATCH 112/150] Fix chunking on whitespace only input --- src/textual/_wrap.py | 2 +- tests/test_wrap.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index ccf93a7b15..1096105190 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -9,7 +9,7 @@ from ._loop import loop_last from .expand_tabs import get_tab_widths -re_chunk = re.compile(r"\s*\S+\s*") +re_chunk = re.compile(r"(\s*)(\S+)(\s*)|(\s+)") def chunks(text: str) -> Iterable[tuple[int, int, str]]: diff --git a/tests/test_wrap.py b/tests/test_wrap.py index b60892ccae..b103091cc4 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -3,8 +3,8 @@ def test_chunks(): assert list(chunks("")) == [] - assert list(chunks(" ")) == [] - assert list(chunks("\t")) == [] + assert list(chunks(" ")) == [(0, 4, " ")] + assert list(chunks("\t")) == [(0, 1, "\t")] assert list(chunks("foo")) == [(0, 3, "foo")] assert list(chunks(" foo ")) == [(0, 7, " foo ")] assert list(chunks("foo bar")) == [(0, 4, "foo "), (4, 7, "bar")] From 57fbd23102ef51517a9f12d2eab0986293596bb2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 10:55:06 +0000 Subject: [PATCH 113/150] Simplify chunk regex --- src/textual/_wrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 1096105190..c44df5a588 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -9,7 +9,7 @@ from ._loop import loop_last from .expand_tabs import get_tab_widths -re_chunk = re.compile(r"(\s*)(\S+)(\s*)|(\s+)") +re_chunk = re.compile(r"\s*\S+\s*|\s+") def chunks(text: str) -> Iterable[tuple[int, int, str]]: From ac7c62959f08dff8f029a3096cd9f5945d4591fb Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 16:46:40 +0000 Subject: [PATCH 114/150] Testing cases where folding is required during wrapping --- src/textual/_wrap.py | 54 +++++++++++++++++++++++++++----------------- tests/test_wrap.py | 51 +++++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index c44df5a588..12f3681c71 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -3,7 +3,7 @@ import re from typing import Iterable -from rich.cells import chop_cells +from rich.cells import get_character_cell_size from ._cells import cell_len from ._loop import loop_last @@ -53,28 +53,19 @@ def divide_line( cell_offset = 0 _cell_len = cell_len - # todo! could we offset the wrap offsets after computing them, by going through them, and summing the widths of - # all tab characters that appeared before the wrap offset on the line? for example if we have a wrap offset of - # 14, then we go through the tab_widths tuples until we’ve seen 14 codepoints, and note the total widths of tabs - # encountered. - - # build mapping of tab positions to tab widths. then, for each chunk, check if a - # get all of the tab - - # foo\tbar\t\baz - - tab_widths = get_tab_widths(text, tab_size) + tab_size = min(tab_size, width) + tab_sections = get_tab_widths(text, tab_size) + tab_section_index = 0 cumulative_widths = [] cumulative_width = 0 - for tab_section, tab_width in tab_widths: - cumulative_widths.extend([cumulative_width] * len(tab_section)) + for tab_section, tab_width in tab_sections: + # add 1 since the \t character is stripped by get_tab_widths + section_codepoint_length = len(tab_section) + 1 + cumulative_widths.extend([cumulative_width] * section_codepoint_length) cumulative_width += tab_width for start, end, chunk in chunks(text): - # todo, 1st, terrible name, 2nd can we get the "word width" here to account - # for tab widths? chunk_width = _cell_len(chunk) - print(start, end, chunk, len(chunk)) tab_width_before_start = cumulative_widths[start] tab_width_before_end = cumulative_widths[end] chunk_tab_width = tab_width_before_end - tab_width_before_start @@ -88,11 +79,32 @@ def divide_line( else: # Not enough space remaining for this word on the current line. if chunk_width > width: - # The word doesn't fit on any line, so we can't simply - # place it on the next line... + # The word doesn't fit on any line, so we must fold it if fold: - # Fold the word across multiple lines. - folded_word = chop_cells(chunk, width=width) + _get_character_cell_size = get_character_cell_size + lines: list[list[str]] = [[]] + + append_new_line = lines.append + append_to_last_line = lines[-1].append + + total_width = 0 + for character in chunk: + if character == "\t": + # Tab characters have dynamic width, so we need to look + cell_width = tab_sections[tab_section_index][1] + tab_section_index += 1 + else: + cell_width = _get_character_cell_size(character) + + if total_width + cell_width > width: + append_new_line([character]) + append_to_last_line = lines[-1].append + total_width = cell_width + else: + append_to_last_line(character) + total_width += cell_width + + folded_word = ["".join(line) for line in lines] for last, line in loop_last(folded_word): if start: append(start) diff --git a/tests/test_wrap.py b/tests/test_wrap.py index b103091cc4..b4d633aebf 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -1,15 +1,36 @@ -from textual._wrap import chunks - - -def test_chunks(): - assert list(chunks("")) == [] - assert list(chunks(" ")) == [(0, 4, " ")] - assert list(chunks("\t")) == [(0, 1, "\t")] - assert list(chunks("foo")) == [(0, 3, "foo")] - assert list(chunks(" foo ")) == [(0, 7, " foo ")] - assert list(chunks("foo bar")) == [(0, 4, "foo "), (4, 7, "bar")] - assert list(chunks("\tfoo bar")) == [(0, 5, "\tfoo "), (5, 8, "bar")] - assert list(chunks(" foo bar")) == [(0, 5, " foo "), (5, 8, "bar")] - assert list(chunks("foo bar ")) == [(0, 4, "foo "), (4, 10, "bar ")] - assert list(chunks("foo\t bar ")) == [(0, 6, "foo\t "), (6, 12, "bar ")] - assert list(chunks("木\t 川 ")) == [(0, 4, "木\t "), (4, 8, "川 ")] +import pytest + +from textual._wrap import chunks, divide_line + + +@pytest.mark.parametrize( + "input_text, expected_output", + [ + ("", []), + (" ", [(0, 4, " ")]), + ("\t", [(0, 1, "\t")]), + ("foo", [(0, 3, "foo")]), + (" foo ", [(0, 7, " foo ")]), + ("foo bar", [(0, 4, "foo "), (4, 7, "bar")]), + ("\tfoo bar", [(0, 5, "\tfoo "), (5, 8, "bar")]), + (" foo bar", [(0, 5, " foo "), (5, 8, "bar")]), + ("foo bar ", [(0, 4, "foo "), (4, 10, "bar ")]), + ("foo\t bar ", [(0, 6, "foo\t "), (6, 12, "bar ")]), + ("木\t 川 ", [(0, 4, "木\t "), (4, 8, "川 ")]), + ], +) +def test_chunks(input_text, expected_output): + assert list(chunks(input_text)) == expected_output + + +@pytest.mark.parametrize( + "text, width, tab_size, expected_output", + [ + ("foo bar baz", 6, 4, [4, 8]), + ("\tfoo bar baz", 6, 4, [3, 9]), + ("\tfo bar baz", 6, 4, [3, 8]), + ("\tfo bar baz", 6, 8, [1, 4, 8]), + ], +) +def test_divide_line(text, width, tab_size, expected_output): + assert divide_line(text, width, tab_size) == expected_output From 4dcfaddc91ad5883dff45ad23765559108653cd0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 16 Jan 2024 17:04:45 +0000 Subject: [PATCH 115/150] Adding more edge case tests --- tests/test_wrap.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_wrap.py b/tests/test_wrap.py index b4d633aebf..000a8c68f2 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -26,10 +26,14 @@ def test_chunks(input_text, expected_output): @pytest.mark.parametrize( "text, width, tab_size, expected_output", [ - ("foo bar baz", 6, 4, [4, 8]), - ("\tfoo bar baz", 6, 4, [3, 9]), - ("\tfo bar baz", 6, 4, [3, 8]), - ("\tfo bar baz", 6, 8, [1, 4, 8]), + # ("", 6, 4, []), + ("\t", 6, 4, []), + ("\t", 6, 4, []), + # (" ", 6, 4, []), + # ("foo bar baz", 6, 4, [4, 8]), + # ("\tfoo bar baz", 6, 4, [3, 9]), + # ("\tfo bar baz", 6, 4, [3, 8]), + # ("\tfo bar baz", 6, 8, [1, 4, 8]), ], ) def test_divide_line(text, width, tab_size, expected_output): From 7412ec7af2658de2c676dc4b58cea68c0719a985 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 17 Jan 2024 10:54:45 +0000 Subject: [PATCH 116/150] Fix an off-by-one in wrapping --- src/textual/_wrap.py | 2 +- tests/test_wrap.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 12f3681c71..6decb8827a 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -67,7 +67,7 @@ def divide_line( for start, end, chunk in chunks(text): chunk_width = _cell_len(chunk) tab_width_before_start = cumulative_widths[start] - tab_width_before_end = cumulative_widths[end] + tab_width_before_end = cumulative_widths[end - 1] chunk_tab_width = tab_width_before_end - tab_width_before_start chunk_width += chunk_tab_width remaining_space = width - cell_offset diff --git a/tests/test_wrap.py b/tests/test_wrap.py index 000a8c68f2..fb0547ccb2 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -26,14 +26,14 @@ def test_chunks(input_text, expected_output): @pytest.mark.parametrize( "text, width, tab_size, expected_output", [ - # ("", 6, 4, []), + ("", 6, 4, []), ("\t", 6, 4, []), - ("\t", 6, 4, []), - # (" ", 6, 4, []), - # ("foo bar baz", 6, 4, [4, 8]), - # ("\tfoo bar baz", 6, 4, [3, 9]), - # ("\tfo bar baz", 6, 4, [3, 8]), - # ("\tfo bar baz", 6, 8, [1, 4, 8]), + (" ", 6, 4, []), + ("foo bar baz", 6, 4, [4, 8]), + ("\tfoo bar baz", 6, 4, [3, 9]), + ("\tfo bar baz", 6, 4, [3, 8]), + ("\tfo bar baz", 6, 8, [1, 4, 8]), + ("\tfo bar baz\t", 6, 8, [1, 4, 8]), ], ) def test_divide_line(text, width, tab_size, expected_output): From 418bc0e80246ff990ad43d6fe18d791f2bc99fb0 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 17 Jan 2024 18:37:34 +0000 Subject: [PATCH 117/150] Fix visual offset calculation when word folds during text area wrapping calculation such that it accounts for tabs --- src/textual/_wrap.py | 14 ++++++++++---- src/textual/app.py | 1 + src/textual/document/_wrapped_document.py | 10 +++++++--- src/textual/widgets/_text_area.py | 6 ++++-- tests/test_wrap.py | 23 +++++++++++++---------- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 6decb8827a..0173aeed10 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -29,7 +29,7 @@ def chunks(text: str) -> Iterable[tuple[int, int, str]]: yield start, end, chunk -def divide_line( +def compute_wrap_offsets( text: str, width: int, tab_size: int, @@ -60,12 +60,12 @@ def divide_line( cumulative_width = 0 for tab_section, tab_width in tab_sections: # add 1 since the \t character is stripped by get_tab_widths - section_codepoint_length = len(tab_section) + 1 + section_codepoint_length = len(tab_section) + int(bool(tab_width)) cumulative_widths.extend([cumulative_width] * section_codepoint_length) cumulative_width += tab_width for start, end, chunk in chunks(text): - chunk_width = _cell_len(chunk) + chunk_width = _cell_len(chunk) # this cell len excludes tabs completely tab_width_before_start = cumulative_widths[start] tab_width_before_end = cumulative_widths[end - 1] chunk_tab_width = tab_width_before_end - tab_width_before_start @@ -109,7 +109,13 @@ def divide_line( if start: append(start) if last: - cell_offset = _cell_len(line) + # Since cell_len ignores tabs, we need to check the width + # of the tabs in this line + line_tab_widths = ( + cumulative_widths[start + len(line) - 1] + - cumulative_widths[start] + ) + cell_offset = _cell_len(line) + line_tab_widths else: start += len(line) else: diff --git a/src/textual/app.py b/src/textual/app.py index 4f679c4787..7e850b22a0 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -424,6 +424,7 @@ def __init__( _environ=environ, force_terminal=True, safe_box=False, + soft_wrap=False, ) self._workers = WorkerManager(self) self.error_console = Console(markup=False, stderr=True) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index d6f6a3c9f0..ddeedb943f 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -12,7 +12,7 @@ from rich.text import Text from textual._cells import cell_len, cell_width_to_column_index -from textual._wrap import divide_line +from textual._wrap import compute_wrap_offsets from textual.document._document import DocumentBase, Location from textual.expand_tabs import expand_tabs_inline from textual.geometry import Offset, clamp @@ -77,7 +77,9 @@ def wrap(self, width: int, tab_width: int) -> None: current_offset = 0 for line_index, line in enumerate(self.document.lines): - wrap_offsets = divide_line(line, width, tab_size=tab_width) if width else [] + wrap_offsets = ( + compute_wrap_offsets(line, width, tab_size=tab_width) if width else [] + ) append_wrap_offset(wrap_offsets) line_index_to_offsets.append([]) for section_y_offset in range(len(wrap_offsets) + 1): @@ -170,7 +172,9 @@ def wrap_range( # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset for line_index, line in enumerate(new_lines, top_line_index): - wrap_offsets = divide_line(line, width, tab_size=tab_width) if width else [] + wrap_offsets = ( + compute_wrap_offsets(line, width, tab_size=tab_width) if width else [] + ) append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index cd0dae9b94..f6438c86ff 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -946,7 +946,6 @@ def render_line(self, widget_y: int) -> Strip: # TODO: Lets not apply the division each time through render_line. # We should cache sections with the edit counts. wrap_offsets = wrapped_document.get_offsets(line_index) - if wrap_offsets: sections = line.divide(wrap_offsets) # TODO cache result with edit count line = sections[section_offset] @@ -960,7 +959,10 @@ def render_line(self, widget_y: int) -> Strip: target_width = base_width - self.gutter_width console = self.app.console gutter_segments = console.render(gutter) - text_segments = console.render(line, console.options.update_width(target_width)) + line.expand_tabs(self.indent_width) + text_segments = list( + console.render(line, console.options.update_width(target_width)) + ) gutter_strip = Strip(gutter_segments, cell_length=gutter_width) text_strip = Strip(text_segments) diff --git a/tests/test_wrap.py b/tests/test_wrap.py index fb0547ccb2..2e3c07aa4b 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -1,6 +1,6 @@ import pytest -from textual._wrap import chunks, divide_line +from textual._wrap import chunks, compute_wrap_offsets @pytest.mark.parametrize( @@ -26,15 +26,18 @@ def test_chunks(input_text, expected_output): @pytest.mark.parametrize( "text, width, tab_size, expected_output", [ - ("", 6, 4, []), - ("\t", 6, 4, []), - (" ", 6, 4, []), - ("foo bar baz", 6, 4, [4, 8]), - ("\tfoo bar baz", 6, 4, [3, 9]), - ("\tfo bar baz", 6, 4, [3, 8]), - ("\tfo bar baz", 6, 8, [1, 4, 8]), - ("\tfo bar baz\t", 6, 8, [1, 4, 8]), + # ("", 6, 4, []), + # ("\t", 6, 4, []), + # (" ", 6, 4, []), + # ("foo bar baz", 6, 4, [4, 8]), + # ("\tfoo bar baz", 6, 4, [3, 9]), + # ("\tfo bar baz", 6, 4, [3, 8]), + # ("\tfo bar baz", 6, 8, [1, 4, 8]), + # ("\tfo bar baz\t", 6, 8, [1, 4, 8]), + # ("\t\t\tfo bar baz\t", 20, 4, [10]), + # ("\t\t\t\t\t\t\t\tfo bar bar", 19, 4, [4, 11]), + ("\t\t\t\t\t", 19, 4, [4, 11]), ], ) def test_divide_line(text, width, tab_size, expected_output): - assert divide_line(text, width, tab_size) == expected_output + assert compute_wrap_offsets(text, width, tab_size) == expected_output From af705f843b557eff3852de83bfb27297ed543b2e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 17 Jan 2024 19:37:09 +0000 Subject: [PATCH 118/150] Fixing off-by-one --- src/textual/_wrap.py | 22 +++++++++++++++------- tests/test_wrap.py | 22 +++++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 0173aeed10..3501b98969 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -56,18 +56,23 @@ def compute_wrap_offsets( tab_size = min(tab_size, width) tab_sections = get_tab_widths(text, tab_size) tab_section_index = 0 - cumulative_widths = [] cumulative_width = 0 - for tab_section, tab_width in tab_sections: + cumulative_widths = [] # Accumulated tab widths of all codepoints prior (exclusive) + record_widths = cumulative_widths.extend + + for last, (tab_section, tab_width) in loop_last(tab_sections): # add 1 since the \t character is stripped by get_tab_widths section_codepoint_length = len(tab_section) + int(bool(tab_width)) - cumulative_widths.extend([cumulative_width] * section_codepoint_length) + widths = [cumulative_width] * section_codepoint_length + record_widths(widths) cumulative_width += tab_width + if last: + cumulative_widths.append(cumulative_width) for start, end, chunk in chunks(text): chunk_width = _cell_len(chunk) # this cell len excludes tabs completely tab_width_before_start = cumulative_widths[start] - tab_width_before_end = cumulative_widths[end - 1] + tab_width_before_end = cumulative_widths[end] chunk_tab_width = tab_width_before_end - tab_width_before_start chunk_width += chunk_tab_width remaining_space = width - cell_offset @@ -90,7 +95,7 @@ def compute_wrap_offsets( total_width = 0 for character in chunk: if character == "\t": - # Tab characters have dynamic width, so we need to look + # Tab characters have dynamic width, so look it up cell_width = tab_sections[tab_section_index][1] tab_section_index += 1 else: @@ -110,9 +115,12 @@ def compute_wrap_offsets( append(start) if last: # Since cell_len ignores tabs, we need to check the width - # of the tabs in this line + # of the tabs in this line. The width of tabs within the + # line is computed by taking the difference between the + # cumulative width of tabs up to the end of the line and the + # cumulative width of tabs up to the start of the line. line_tab_widths = ( - cumulative_widths[start + len(line) - 1] + cumulative_widths[start + len(line)] - cumulative_widths[start] ) cell_offset = _cell_len(line) + line_tab_widths diff --git a/tests/test_wrap.py b/tests/test_wrap.py index 2e3c07aa4b..5a5d2b59f9 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -26,17 +26,17 @@ def test_chunks(input_text, expected_output): @pytest.mark.parametrize( "text, width, tab_size, expected_output", [ - # ("", 6, 4, []), - # ("\t", 6, 4, []), - # (" ", 6, 4, []), - # ("foo bar baz", 6, 4, [4, 8]), - # ("\tfoo bar baz", 6, 4, [3, 9]), - # ("\tfo bar baz", 6, 4, [3, 8]), - # ("\tfo bar baz", 6, 8, [1, 4, 8]), - # ("\tfo bar baz\t", 6, 8, [1, 4, 8]), - # ("\t\t\tfo bar baz\t", 20, 4, [10]), - # ("\t\t\t\t\t\t\t\tfo bar bar", 19, 4, [4, 11]), - ("\t\t\t\t\t", 19, 4, [4, 11]), + ("", 6, 4, []), + ("\t", 6, 4, []), + (" ", 6, 4, []), + ("foo bar baz", 6, 4, [4, 8]), + ("\tfoo bar baz", 6, 4, [3, 9]), + ("\tfo bar baz", 6, 4, [3, 8]), + ("\tfo bar baz", 6, 8, [1, 4, 8]), + ("\tfo bar baz\t", 6, 8, [1, 4, 8]), + ("\t\t\tfo bar baz\t", 20, 4, [10]), + ("\t\t\t\t\t\t\t\tfo bar bar", 19, 4, [4, 11]), + ("\t\t\t\t\t", 19, 4, [4]), ], ) def test_divide_line(text, width, tab_size, expected_output): From 6e5f199cc7b6262b5ac626f45656ef2e15948c41 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 10:18:00 +0000 Subject: [PATCH 119/150] Fix chunk re --- src/textual/_wrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 3501b98969..31dbc0286a 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -9,7 +9,7 @@ from ._loop import loop_last from .expand_tabs import get_tab_widths -re_chunk = re.compile(r"\s*\S+\s*|\s+") +re_chunk = re.compile(r"\S+\s*|\s+") def chunks(text: str) -> Iterable[tuple[int, int, str]]: From 84f3934bf39cea20310405771ac056a39d8670be Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 10:37:21 +0000 Subject: [PATCH 120/150] Fix wrapped document tests --- src/textual/document/_wrapped_document.py | 19 +++++++++++-------- tests/document/test_wrapped_document.py | 22 +++++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index ddeedb943f..0b5fb13d33 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -57,16 +57,21 @@ def __init__( """The width the document is currently wrapped at. This will correspond with the value last passed into the `wrap` method.""" + self._tab_width: int = tab_width + self.wrap(width, tab_width) - def wrap(self, width: int, tab_width: int) -> None: + def wrap(self, width: int, tab_width: int | None = None) -> None: """Wrap and cache all lines in the document. Args: width: The width to wrap at. 0 for no wrapping. - tab_width: The maximum width to consider for tab characters. + tab_width: The maximum width to consider for tab characters. If None, + reuse the tab width. """ self._width = width + if tab_width: + self._tab_width = tab_width # We're starting wrapping from scratch new_wrap_offsets: list[list[int]] = [] @@ -120,7 +125,6 @@ def wrap_range( start: Location, old_end: Location, new_end: Location, - tab_width: int, ) -> None: """Incrementally recompute wrapping based on a performed edit. @@ -130,7 +134,6 @@ def wrap_range( start: The start location of the edit that was performed in document-space. old_end: The old end location of the edit in document-space. new_end: The new end location of the edit in document-space. - tab_width: The maximum width to consider for tab characters. """ # Get all the text on the lines between start and end in document space @@ -171,10 +174,9 @@ def wrap_range( # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset + tab_width = self._tab_width for line_index, line in enumerate(new_lines, top_line_index): - wrap_offsets = ( - compute_wrap_offsets(line, width, tab_size=tab_width) if width else [] - ) + wrap_offsets = compute_wrap_offsets(line, width, tab_width) if width else [] append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line @@ -226,7 +228,7 @@ def wrap_range( top_line_index : old_bottom_line_index + 1 ] = new_wrap_offsets - def offset_to_location(self, offset: Offset, tab_width: int) -> Location: + def offset_to_location(self, offset: Offset) -> Location: """Given an offset within the wrapped/visual display of the document, return the corresponding location in the document. @@ -260,6 +262,7 @@ def offset_to_location(self, offset: Offset, tab_width: int) -> Location: # y-offset is too large offset_data = self._offset_to_line_info[-1] + tab_width = self._tab_width if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( diff --git a/tests/document/test_wrapped_document.py b/tests/document/test_wrapped_document.py index 1a62a5aa85..2acc5f3358 100644 --- a/tests/document/test_wrapped_document.py +++ b/tests/document/test_wrapped_document.py @@ -50,7 +50,9 @@ def test_refresh_range(): # Inform the wrapped document about the range impacted by the edit wrapped_document.wrap_range( - start_location, old_end_location, edit_result.end_location + start_location, + old_end_location, + edit_result.end_location, ) # Now confirm the resulting wrapped version is as we would expect @@ -134,7 +136,7 @@ def test_offset_to_location_wrapping_enabled(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - assert wrapped_document.offset_to_location(offset, 2) == location + assert wrapped_document.offset_to_location(offset) == location @pytest.mark.parametrize( @@ -153,16 +155,22 @@ def test_offset_to_location_wrapping_disabled(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=0) - assert wrapped_document.offset_to_location(offset, 4) == location + assert wrapped_document.offset_to_location(offset) == location -@pytest.mark.parametrize("offset", [Offset(-3, 0), Offset(0, -10)]) -def test_offset_to_location_invalid_offset_raises_exception(offset): +@pytest.mark.parametrize( + "offset,location", + [ + [Offset(-3, 0), (0, 0)], + [Offset(0, -10), (0, 0)], + ], +) +def test_offset_to_location_invalid_offset_clamps_to_valid_offset(offset, location): document = Document(SIMPLE_TEXT) wrapped_document = WrappedDocument(document, width=4) - with pytest.raises(ValueError): - wrapped_document.offset_to_location(offset, 10) + result = wrapped_document.offset_to_location(offset) + assert result == location @pytest.mark.parametrize( From 0d87caa2ea6adccba55858c4c7f2b20b0f2d166a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 10:54:00 +0000 Subject: [PATCH 121/150] Updating some more tests --- src/textual/document/_document_navigator.py | 20 ++++++++------------ src/textual/document/_wrapped_document.py | 15 +++++---------- src/textual/widgets/_text_area.py | 19 ++++++++----------- tests/document/test_document_navigator.py | 4 ++-- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index b58363ba11..0d5deb7b15 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -165,7 +165,7 @@ def get_location_right(self, location: Location) -> Location: return target_row, target_column def get_location_above( - self, location: Location, tab_width: int, maintain_offset: bool = False + self, location: Location, maintain_offset: bool = False ) -> Location: """Get the location up from the given location in the wrapped document.""" @@ -193,7 +193,7 @@ def get_location_above( # Get the last section from the line above, and find where to move in it. target_row = line_index - 1 target_column = self._wrapped_document.get_target_document_column( - target_row, target_offset, -1, tab_width + target_row, target_offset, -1 ) target_location = target_row, target_column else: @@ -201,14 +201,14 @@ def get_location_above( # Since the section above could be shorter, we need to clamp the column # to a valid value. target_column = self._wrapped_document.get_target_document_column( - line_index, target_offset, section_index - 1, tab_width + line_index, target_offset, section_index - 1 ) target_location = line_index, target_column return target_location def get_location_below( - self, location: Location, tab_width: int, maintain_offset: bool = True + self, location: Location, maintain_offset: bool = True ) -> Location: """Given a location in the raw document, return the raw document location corresponding to moving down in the wrapped representation @@ -216,7 +216,6 @@ def get_location_below( Args: location: The location in the raw document. - tab_width: The width of the tab stops. maintain_offset: Maintain the visual x-offset of the cursor. Returns: @@ -243,14 +242,14 @@ def get_location_below( # Go to the first section of the line below. target_row = line_index + 1 target_column = self._wrapped_document.get_target_document_column( - target_row, target_offset, 0, tab_width + target_row, target_offset, 0 ) target_location = target_row, target_column else: # Stay on the same document line, but move forwards to # the location on the section below with the same visual offset. target_column = self._wrapped_document.get_target_document_column( - line_index, target_offset, section_index + 1, tab_width + line_index, target_offset, section_index + 1 ) target_location = line_index, target_column @@ -307,16 +306,13 @@ def get_location_home( return line_index, 0 def get_location_offset_relative( - self, location: Location, offset_delta: int, tab_width: int + self, location: Location, offset_delta: int ) -> Location: # Convert into offset-space to apply the offset. - x_offset, y_offset = self._wrapped_document.location_to_offset( - location, tab_width - ) + x_offset, y_offset = self._wrapped_document.location_to_offset(location) # Convert the offset with the delta applied back to location-space. return self._wrapped_document.offset_to_location( Offset(x_offset, y_offset + offset_delta), - tab_width, ) def clamp_reachable(self, location: Location) -> Location: diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 0b5fb13d33..0e737bc822 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -81,6 +81,7 @@ def wrap(self, width: int, tab_width: int | None = None) -> None: append_wrap_offset = new_wrap_offsets.append current_offset = 0 + tab_width = self._tab_width for line_index, line in enumerate(self.document.lines): wrap_offsets = ( compute_wrap_offsets(line, width, tab_size=tab_width) if width else [] @@ -234,7 +235,6 @@ def offset_to_location(self, offset: Offset) -> Location: Args: offset: The y-offset within the document. - tab_width: The maximum width of tab characters in the document. Raises: ValueError: When the given offset does not correspond to a line @@ -262,30 +262,27 @@ def offset_to_location(self, offset: Offset) -> Location: # y-offset is too large offset_data = self._offset_to_line_info[-1] - tab_width = self._tab_width if offset_data is not None: line_index, section_y = offset_data location = line_index, get_target_document_column( line_index, x, section_y, - tab_width, ) else: location = len(self._wrap_offsets) - 1, get_target_document_column( - -1, x, -1, tab_width + -1, x, -1 ) # Offset doesn't match any line => land on bottom wrapped line return location - def location_to_offset(self, location: Location, tab_width: int) -> Offset: + def location_to_offset(self, location: Location) -> Offset: """ Convert a location in the document to an offset within the wrapped/visual display of the document. Args: location: The location in the document. - tab_width: The maximum width of tab characters in the document. Returns: The Offset in the document's visual display corresponding to the given location. @@ -306,7 +303,7 @@ def location_to_offset(self, location: Location, tab_width: int) -> Offset: section = self.get_sections(line_index)[section_index] x_offset = cell_len( - expand_tabs_inline(section[:section_column_index], tab_width) + expand_tabs_inline(section[:section_column_index], self._tab_width) ) return Offset(x_offset, y_offsets[section_index]) @@ -316,7 +313,6 @@ def get_target_document_column( line_index: int, x_offset: int, y_offset: int, - tab_width: int, ) -> int: """Given a line index and the offsets within the wrapped version of that line, return the corresponding column index in the raw document. @@ -325,7 +321,6 @@ def get_target_document_column( line_index: The index of the line in the document. x_offset: The x-offset within the wrapped line. y_offset: The y-offset within the wrapped line (supports negative indexing). - tab_width: The size of the tab stop. Returns: The column index corresponding to the line index and y offset. @@ -348,7 +343,7 @@ def get_target_document_column( # Get the column index within this wrapped section of the line target_column_index = target_section_start + cell_width_to_column_index( - target_section, x_offset, tab_width + target_section, x_offset, self._tab_width ) # If we're on the final section of a line, the cursor can legally rest beyond diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f6438c86ff..6007f9c07a 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -432,7 +432,7 @@ def _watch_selection(self, selection: Selection) -> None: def _recompute_cursor_offset(self): """Recompute the (x, y) coordinate of the cursor in the wrapped document.""" self._cursor_offset = self.wrapped_document.location_to_offset( - self.cursor_location, self.indent_width + self.cursor_location ) def find_matching_bracket( @@ -1042,7 +1042,6 @@ def edit(self, edit: Edit) -> EditResult: edit.from_location, edit.to_location, result.end_location, - self.indent_width, ) self._refresh_size() @@ -1449,9 +1448,7 @@ def get_cursor_down_location(self) -> Location: Returns: The location the cursor will move to if it moves down. """ - return self._navigator.get_location_below( - self.cursor_location, self.indent_width - ) + return self._navigator.get_location_below(self.cursor_location) def action_cursor_up(self, select: bool = False) -> None: """Move the cursor up one cell. @@ -1468,9 +1465,7 @@ def get_cursor_up_location(self) -> Location: Returns: The location the cursor will move to if it moves up. """ - return self._navigator.get_location_above( - self.cursor_location, self.indent_width - ) + return self._navigator.get_location_above(self.cursor_location) def action_cursor_line_end(self, select: bool = False) -> None: """Move the cursor to the end of the line.""" @@ -1574,7 +1569,8 @@ def action_cursor_page_up(self) -> None: height = self.content_size.height _, cursor_location = self.selection target = self._navigator.get_location_offset_relative( - cursor_location, -height, self.indent_width + cursor_location, + -height, ) self.scroll_relative(y=-height, animate=False) self.move_cursor(target) @@ -1584,7 +1580,8 @@ def action_cursor_page_down(self) -> None: height = self.content_size.height _, cursor_location = self.selection target = self._navigator.get_location_offset_relative( - cursor_location, height, self.indent_width + cursor_location, + height, ) self.scroll_relative(y=height, animate=False) self.move_cursor(target) @@ -1611,7 +1608,7 @@ def record_cursor_width(self) -> None: jump back to the same offset that we were originally at. """ cursor_x_offset, _ = self.wrapped_document.location_to_offset( - self.cursor_location, self.indent_width + self.cursor_location ) self._navigator.last_x_offset = cursor_x_offset diff --git a/tests/document/test_document_navigator.py b/tests/document/test_document_navigator.py index 472094d40c..d5dbe616e5 100644 --- a/tests/document/test_document_navigator.py +++ b/tests/document/test_document_navigator.py @@ -41,7 +41,7 @@ def make_navigator(text, width): ], ) def test_get_location_above(start, end): - assert make_navigator(TEXT, 4).get_location_above(start, 4) == end + assert make_navigator(TEXT, 4).get_location_above(start) == end @pytest.mark.parametrize( @@ -59,7 +59,7 @@ def test_get_location_above(start, end): ], ) def test_get_location_below(start, end): - assert make_navigator(TEXT, 4).get_location_below(start, 4) == end + assert make_navigator(TEXT, 4).get_location_below(start) == end @pytest.mark.parametrize( From 37db462a0bfe74536deacd82c33d193247a75710 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 11:04:34 +0000 Subject: [PATCH 122/150] Fix issue with new delete line implementation --- src/textual/widgets/_text_area.py | 2 +- tests/text_area/test_edit_via_bindings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 6007f9c07a..f38c92c93d 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1730,7 +1730,7 @@ def action_delete_line(self) -> None: end_row -= 1 from_location = (start_row, 0) - to_location = (end_row + 1, end_column) + to_location = (end_row + 1, 0) self.delete(from_location, to_location, maintain_selection_offset=False) self.move_cursor_relative(columns=end_column, record_width=False) diff --git a/tests/text_area/test_edit_via_bindings.py b/tests/text_area/test_edit_via_bindings.py index aa99a63ad9..b4a3e6444f 100644 --- a/tests/text_area/test_edit_via_bindings.py +++ b/tests/text_area/test_edit_via_bindings.py @@ -219,8 +219,8 @@ async def test_delete_line_multiline_document(selection, expected_result): await pilot.press("ctrl+x") - cursor_row, _ = text_area.cursor_location - assert text_area.selection == Selection.cursor((cursor_row, 0)) + cursor_row, cursor_column = text_area.cursor_location + assert text_area.selection == Selection.cursor((cursor_row, cursor_column)) assert text_area.text == expected_result From 910c62c5448de03769722970de2aa52d8051f387 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 11:32:51 +0000 Subject: [PATCH 123/150] Fixing smart home --- src/textual/document/_document_navigator.py | 2 +- src/textual/widgets/_text_area.py | 6 +- .../__snapshots__/test_snapshots.ambr | 2514 ++++++++--------- 3 files changed, 1260 insertions(+), 1262 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 0d5deb7b15..ccac2f23be 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -300,7 +300,7 @@ def get_location_home( target_column = index break - if column_offset <= target_column and column_offset != 0: + if column_offset == 0 or column_offset > target_column: return line_index, target_column return line_index, 0 diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f38c92c93d..2dbbb41b38 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1106,9 +1106,7 @@ def get_target_document_location(self, event: MouseEvent) -> Location: scroll_x, scroll_y = self.scroll_offset target_x = event.x - self.gutter_width + scroll_x - self.gutter.left target_y = event.y + scroll_y - self.gutter.top - location = self.wrapped_document.offset_to_location( - Offset(target_x, target_y), self.indent_width - ) + location = self.wrapped_document.offset_to_location(Offset(target_x, target_y)) return location @property @@ -1738,7 +1736,7 @@ def action_delete_line(self) -> None: def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" from_location = self.selection.end - to_location = self.get_cursor_line_start_location() + to_location = self.get_cursor_line_start_location(smart_home=True) self.delete(from_location, to_location, maintain_selection_offset=False) def action_delete_to_end_of_line(self) -> None: diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index cbf0099dca..06b1225c68 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -33216,319 +33216,319 @@ font-weight: 700; } - .terminal-2148777132-matrix { + .terminal-1267383468-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2148777132-title { + .terminal-1267383468-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2148777132-r1 { fill: #c2c2bf } - .terminal-2148777132-r2 { fill: #272822 } - .terminal-2148777132-r3 { fill: #75715e } - .terminal-2148777132-r4 { fill: #f8f8f2 } - .terminal-2148777132-r5 { fill: #c5c8c6 } - .terminal-2148777132-r6 { fill: #90908a } - .terminal-2148777132-r7 { fill: #e6db74 } - .terminal-2148777132-r8 { fill: #ae81ff } - .terminal-2148777132-r9 { fill: #f92672 } - .terminal-2148777132-r10 { fill: #a6e22e } + .terminal-1267383468-r1 { fill: #c2c2bf } + .terminal-1267383468-r2 { fill: #272822 } + .terminal-1267383468-r3 { fill: #75715e } + .terminal-1267383468-r4 { fill: #f8f8f2 } + .terminal-1267383468-r5 { fill: #c5c8c6 } + .terminal-1267383468-r6 { fill: #90908a } + .terminal-1267383468-r7 { fill: #e6db74 } + .terminal-1267383468-r8 { fill: #ae81ff } + .terminal-1267383468-r9 { fill: #f92672 } + .terminal-1267383468-r10 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  /* This is a comment in CSS */ -  2   -  3  /* Basic selectors and properties */ -  4  body {                                 -  5      font-family: Arial, sans-serif;    -  6      background-color: #f4f4f4;         -  7      margin: 0;                         -  8      padding: 0;                        -  9  }                                      - 10   - 11  /* Class and ID selectors */ - 12  .header {                              - 13      background-color: #333;            - 14      color: #fff;                       - 15      padding: 10px0;                   - 16      text-align: center;                - 17  }                                      - 18   - 19  #logo {                                - 20      font-size: 24px;                   - 21      font-weight: bold;                 - 22  }                                      - 23   - 24  /* Descendant and child selectors */ - 25  .nav ul {                              - 26      list-style-type: none;             - 27      padding: 0;                        - 28  }                                      - 29   - 30  .nav > li {                            - 31      display: inline-block;             - 32      margin-right: 10px;                - 33  }                                      - 34   - 35  /* Pseudo-classes */ - 36  a:hover {                              - 37      text-decoration: underline;        - 38  }                                      - 39   - 40  input:focus {                          - 41      border-color: #007BFF;             - 42  }                                      - 43   - 44  /* Media query */ - 45  @media (max-width: 768px) {            - 46      body {                             - 47          font-size: 16px;               - 48      }                                  - 49   - 50      .header {                          - 51          padding: 5px0;                - 52      }                                  - 53  }                                      - 54   - 55  /* Keyframes animation */ - 56  @keyframes slideIn {                   - 57  from {                             - 58          transform: translateX(-100%);  - 59      }                                  - 60  to {                               - 61          transform: translateX(0);      - 62      }                                  - 63  }                                      - 64   - 65  .slide-in-element {                    - 66      animation: slideIn 0.5s forwards;  - 67  }                                      - 68   + + + +  1  /* This is a comment in CSS */ +  2   +  3  /* Basic selectors and properties */ +  4  body {  +  5      font-family: Arial, sans-serif;  +  6      background-color: #f4f4f4 +  7      margin: 0 +  8      padding: 0 +  9   + 10   + 11  /* Class and ID selectors */ + 12  .header {  + 13      background-color: #333 + 14      color: #fff + 15      padding: 10px0 + 16      text-align: center;  + 17   + 18   + 19  #logo {  + 20      font-size: 24px + 21      font-weight: bold;  + 22   + 23   + 24  /* Descendant and child selectors */ + 25  .nav ul {  + 26      list-style-type: none;  + 27      padding: 0 + 28   + 29   + 30  .nav > li {  + 31      display: inline-block;  + 32      margin-right: 10px + 33   + 34   + 35  /* Pseudo-classes */ + 36  a:hover {  + 37      text-decoration: underline;  + 38   + 39   + 40  input:focus {  + 41      border-color: #007BFF + 42   + 43   + 44  /* Media query */ + 45  @media (max-width: 768px) {  + 46      body {  + 47          font-size: 16px + 48      }  + 49   + 50      .header {  + 51          padding: 5px0 + 52      }  + 53   + 54   + 55  /* Keyframes animation */ + 56  @keyframes slideIn {  + 57  from {  + 58          transform: translateX(-100%);  + 59      }  + 60  to {  + 61          transform: translateX(0);  + 62      }  + 63   + 64   + 65  .slide-in-element {  + 66      animation: slideIn 0.5s forwards;  + 67   + 68   @@ -33559,273 +33559,273 @@ font-weight: 700; } - .terminal-1948245288-matrix { + .terminal-2603932968-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1948245288-title { + .terminal-2603932968-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1948245288-r1 { fill: #c2c2bf } - .terminal-1948245288-r2 { fill: #272822 } - .terminal-1948245288-r3 { fill: #f8f8f2 } - .terminal-1948245288-r4 { fill: #c5c8c6 } - .terminal-1948245288-r5 { fill: #90908a } - .terminal-1948245288-r6 { fill: #f92672 } - .terminal-1948245288-r7 { fill: #e6db74 } - .terminal-1948245288-r8 { fill: #75715e } + .terminal-2603932968-r1 { fill: #c2c2bf } + .terminal-2603932968-r2 { fill: #272822 } + .terminal-2603932968-r3 { fill: #f8f8f2 } + .terminal-2603932968-r4 { fill: #c5c8c6 } + .terminal-2603932968-r5 { fill: #90908a } + .terminal-2603932968-r6 { fill: #f92672 } + .terminal-2603932968-r7 { fill: #e6db74 } + .terminal-2603932968-r8 { fill: #75715e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  <!DOCTYPE html>                                                              -  2  <html lang="en">                                                            -  3   -  4  <head>                                                                      -  5  <!-- Meta tags --> -  6      <meta charset="UTF-8">                                                  -  7      <meta name="viewport" content="width=device-width, initial-scale=1.0" -  8  <!-- Title --> -  9      <title>HTML Test Page</title>                                           - 10  <!-- Link to CSS --> - 11      <link rel="stylesheet" href="styles.css">                               - 12  </head>                                                                     - 13   - 14  <body>                                                                      - 15  <!-- Header section --> - 16      <header class="header">                                                 - 17          <h1 id="logo">HTML Test Page</h1>                                   - 18      </header>                                                               - 19   - 20  <!-- Navigation --> - 21      <nav class="nav">                                                       - 22          <ul>                                                                - 23              <li><a href="#">Home</a></li>                                   - 24              <li><a href="#">About</a></li>                                  - 25              <li><a href="#">Contact</a></li>                                - 26          </ul>                                                               - 27      </nav>                                                                  - 28   - 29  <!-- Main content area --> - 30      <main>                                                                  - 31          <article>                                                           - 32              <h2>Welcome to the Test Page</h2>                               - 33              <p>This is a paragraph to test the HTML structure.</p>          - 34              <img src="test-image.jpg" alt="Test Image" width="300">         - 35          </article>                                                          - 36      </main>                                                                 - 37   - 38  <!-- Form --> - 39      <section>                                                               - 40          <form action="/submit" method="post">                               - 41              <label for="name">Name:</label>                                 - 42              <input type="text" id="name" name="name">                       - 43              <input type="submit" value="Submit">                            - 44          </form>                                                             - 45      </section>                                                              - 46   - 47  <!-- Footer --> - 48      <footer>                                                                - 49          <p>&copy; 2023 HTML Test Page</p>                                   - 50      </footer>                                                               - 51   - 52  <!-- Script tag --> - 53      <script src="scripts.js"></script>                                      - 54  </body>                                                                     - 55   - 56  </html>                                                                     - 57   + + + +  1  <!DOCTYPE html>                                                              +  2  <html lang="en" +  3   +  4  <head +  5  <!-- Meta tags --> +  6      <meta charset="UTF-8" +  7      <meta name="viewport" content="width=device-width, initial-scale=1.0" +  8  <!-- Title --> +  9      <title>HTML Test Page</title + 10  <!-- Link to CSS --> + 11      <link rel="stylesheet" href="styles.css" + 12  </head + 13   + 14  <body + 15  <!-- Header section --> + 16      <header class="header" + 17          <h1 id="logo">HTML Test Page</h1 + 18      </header + 19   + 20  <!-- Navigation --> + 21      <nav class="nav" + 22          <ul + 23              <li><a href="#">Home</a></li + 24              <li><a href="#">About</a></li + 25              <li><a href="#">Contact</a></li + 26          </ul + 27      </nav + 28   + 29  <!-- Main content area --> + 30      <main + 31          <article + 32              <h2>Welcome to the Test Page</h2 + 33              <p>This is a paragraph to test the HTML structure.</p + 34              <img src="test-image.jpg" alt="Test Image" width="300" + 35          </article + 36      </main + 37   + 38  <!-- Form --> + 39      <section + 40          <form action="/submit" method="post" + 41              <label for="name">Name:</label + 42              <input type="text" id="name" name="name" + 43              <input type="submit" value="Submit" + 44          </form + 45      </section + 46   + 47  <!-- Footer --> + 48      <footer + 49          <p>&copy; 2023 HTML Test Page</p + 50      </footer + 51   + 52  <!-- Script tag --> + 53      <script src="scripts.js"></script + 54  </body + 55   + 56  </html + 57   @@ -33856,171 +33856,171 @@ font-weight: 700; } - .terminal-3481240769-matrix { + .terminal-1503429825-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3481240769-title { + .terminal-1503429825-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3481240769-r1 { fill: #c2c2bf } - .terminal-3481240769-r2 { fill: #272822;font-weight: bold } - .terminal-3481240769-r3 { fill: #f8f8f2 } - .terminal-3481240769-r4 { fill: #c5c8c6 } - .terminal-3481240769-r5 { fill: #90908a } - .terminal-3481240769-r6 { fill: #f92672;font-weight: bold } - .terminal-3481240769-r7 { fill: #e6db74 } - .terminal-3481240769-r8 { fill: #ae81ff } - .terminal-3481240769-r9 { fill: #66d9ef;font-style: italic; } - .terminal-3481240769-r10 { fill: #f8f8f2;font-weight: bold } + .terminal-1503429825-r1 { fill: #c2c2bf } + .terminal-1503429825-r2 { fill: #272822;font-weight: bold } + .terminal-1503429825-r3 { fill: #f8f8f2 } + .terminal-1503429825-r4 { fill: #c5c8c6 } + .terminal-1503429825-r5 { fill: #90908a } + .terminal-1503429825-r6 { fill: #f92672;font-weight: bold } + .terminal-1503429825-r7 { fill: #e6db74 } + .terminal-1503429825-r8 { fill: #ae81ff } + .terminal-1503429825-r9 { fill: #66d9ef;font-style: italic; } + .terminal-1503429825-r10 { fill: #f8f8f2;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  { -  2  "name""John Doe",                            -  3  "age"30,                                     -  4  "isStudent"false,                            -  5  "address": {                                   -  6  "street""123 Main St",                   -  7  "city""Anytown",                         -  8  "state""CA",                             -  9  "zip""12345" - 10      },                                             - 11  "phoneNumbers": [                              - 12          {                                          - 13  "type""home",                        - 14  "number""555-555-1234" - 15          },                                         - 16          {                                          - 17  "type""work",                        - 18  "number""555-555-5678" - 19          }                                          - 20      ],                                             - 21  "hobbies": ["reading""hiking""swimming"],  - 22  "pets": [                                      - 23          {                                          - 24  "type""dog",                         - 25  "name""Fido" - 26          },                                         - 27      ],                                             - 28  "graduationYear"null - 29  } - 30   - 31   + + + +  1  { +  2  "name""John Doe" +  3  "age"30 +  4  "isStudent"false +  5  "address": {  +  6  "street""123 Main St" +  7  "city""Anytown" +  8  "state""CA" +  9  "zip""12345" + 10      },  + 11  "phoneNumbers": [  + 12          {  + 13  "type""home" + 14  "number""555-555-1234" + 15          },  + 16          {  + 17  "type""work" + 18  "number""555-555-5678" + 19          }  + 20      ],  + 21  "hobbies": ["reading""hiking""swimming"],  + 22  "pets": [  + 23          {  + 24  "type""dog" + 25  "name""Fido" + 26          },  + 27      ],  + 28  "graduationYear"null + 29  } + 30   + 31   @@ -34051,322 +34051,322 @@ font-weight: 700; } - .terminal-567647262-matrix { + .terminal-2647978975-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-567647262-title { + .terminal-2647978975-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-567647262-r1 { fill: #c2c2bf } - .terminal-567647262-r2 { fill: #272822;font-weight: bold } - .terminal-567647262-r3 { fill: #f92672;font-weight: bold } - .terminal-567647262-r4 { fill: #f8f8f2 } - .terminal-567647262-r5 { fill: #c5c8c6 } - .terminal-567647262-r6 { fill: #90908a } - .terminal-567647262-r7 { fill: #f8f8f2;font-style: italic; } - .terminal-567647262-r8 { fill: #f8f8f2;font-weight: bold } - .terminal-567647262-r9 { fill: #e6db74 } - .terminal-567647262-r10 { fill: #75715e } - .terminal-567647262-r11 { fill: #66d9ef;text-decoration: underline; } - .terminal-567647262-r12 { fill: #23568b } + .terminal-2647978975-r1 { fill: #c2c2bf } + .terminal-2647978975-r2 { fill: #272822;font-weight: bold } + .terminal-2647978975-r3 { fill: #f92672;font-weight: bold } + .terminal-2647978975-r4 { fill: #f8f8f2 } + .terminal-2647978975-r5 { fill: #c5c8c6 } + .terminal-2647978975-r6 { fill: #90908a } + .terminal-2647978975-r7 { fill: #f8f8f2;font-style: italic; } + .terminal-2647978975-r8 { fill: #f8f8f2;font-weight: bold } + .terminal-2647978975-r9 { fill: #e6db74 } + .terminal-2647978975-r10 { fill: #75715e } + .terminal-2647978975-r11 { fill: #66d9ef;text-decoration: underline; } + .terminal-2647978975-r12 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  Heading -  2  =======                                                                      -  3   -  4  Sub-heading -  5  -----------                                                                  -  6   -  7  ### Heading -  8   -  9  #### H4 Heading - 10   - 11  ##### H5 Heading - 12   - 13  ###### H6 Heading - 14   - 15   - 16  Paragraphs are separated                                                     - 17  by a blank line.                                                             - 18   - 19  Two spaces at the end of a line                                              - 20  produces a line break.                                                       - 21   - 22  Text attributes _italic_,                                                    - 23  **bold**`monospace`.                                                       - 24   - 25  Horizontal rule:                                                             - 26   - 27  ---                                                                          - 28   - 29  Bullet list:                                                                 - 30   - 31  * apples                                                                   - 32  * oranges                                                                  - 33  * pears                                                                    - 34   - 35  Numbered list:                                                               - 36   - 37  1. lather                                                                  - 38  2. rinse                                                                   - 39  3. repeat                                                                  - 40   - 41  An [example](http://example.com).                                            - 42   - 43  > Markdown uses email-style > characters for blockquoting.                   - 44  >                                                                            - 45  > Lorem ipsum                                                                - 46   - 47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) - 48   - 49   - 50  ```                                                                          - 51  a=1                                                                          - 52  ```                                                                          - 53   - 54  ```python                                                                    - 55  import this                                                                  - 56  ```                                                                          - 57   - 58  ```somelang                                                                  - 59  foobar                                                                       - 60  ```                                                                          - 61   - 62      import this                                                              - 63   - 64   - 65  1. List item                                                                 - 66   - 67         Code block                                                            - 68   - + + + +  1  Heading +  2  =======  +  3   +  4  Sub-heading +  5  -----------  +  6   +  7  ### Heading +  8   +  9  #### H4 Heading + 10   + 11  ##### H5 Heading + 12   + 13  ###### H6 Heading + 14   + 15   + 16  Paragraphs are separated  + 17  by a blank line.  + 18   + 19  Two spaces at the end of a line    + 20  produces a line break.  + 21   + 22  Text attributes _italic_,   + 23  **bold**`monospace` + 24   + 25  Horizontal rule:  + 26   + 27  ---  + 28   + 29  Bullet list:  + 30   + 31  * apples  + 32  * oranges  + 33  * pears  + 34   + 35  Numbered list:  + 36   + 37  1. lather  + 38  2. rinse  + 39  3. repeat  + 40   + 41  An [example](http://example.com) + 42   + 43  > Markdown uses email-style > characters for blockquoting.  + 44   + 45  > Lorem ipsum  + 46   + 47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) + 48   + 49   + 50  ```  + 51  a=1  + 52  ```  + 53   + 54  ```python  + 55  import this  + 56  ```  + 57   + 58  ```somelang  + 59  foobar  + 60  ```  + 61   + 62      import this  + 63   + 64   + 65  1. List item  + 66   + 67         Code block  + 68   + @@ -34396,363 +34396,363 @@ font-weight: 700; } - .terminal-1039616752-matrix { + .terminal-3976562345-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1039616752-title { + .terminal-3976562345-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1039616752-r1 { fill: #c2c2bf } - .terminal-1039616752-r2 { fill: #272822 } - .terminal-1039616752-r3 { fill: #f92672 } - .terminal-1039616752-r4 { fill: #f8f8f2 } - .terminal-1039616752-r5 { fill: #c5c8c6 } - .terminal-1039616752-r6 { fill: #90908a } - .terminal-1039616752-r7 { fill: #75715e } - .terminal-1039616752-r8 { fill: #e6db74 } - .terminal-1039616752-r9 { fill: #ae81ff } - .terminal-1039616752-r10 { fill: #a6e22e } + .terminal-3976562345-r1 { fill: #c2c2bf } + .terminal-3976562345-r2 { fill: #272822 } + .terminal-3976562345-r3 { fill: #f92672 } + .terminal-3976562345-r4 { fill: #f8f8f2 } + .terminal-3976562345-r5 { fill: #c5c8c6 } + .terminal-3976562345-r6 { fill: #90908a } + .terminal-3976562345-r7 { fill: #75715e } + .terminal-3976562345-r8 { fill: #e6db74 } + .terminal-3976562345-r9 { fill: #ae81ff } + .terminal-3976562345-r10 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  import math                                                                  -  2  from os import path                                                          -  3   -  4  # I'm a comment :) -  5   -  6  string_var ="Hello, world!" -  7  int_var =42 -  8  float_var =3.14 -  9  complex_var =1+2j - 10   - 11  list_var = [12345]                                                   - 12  tuple_var = (12345)                                                  - 13  set_var = {12345}                                                    - 14  dict_var = {"a"1"b"2"c"3}                                          - 15   - 16  deffunction_no_args():                                                      - 17  return"No arguments" - 18   - 19  deffunction_with_args(a, b):                                                - 20  return a + b                                                             - 21   - 22  deffunction_with_default_args(a=0, b=0):                                    - 23  return a * b                                                             - 24   - 25  lambda_func =lambda x: x**2 - 26   - 27  if int_var ==42:                                                            - 28  print("It's the answer!")                                                - 29  elif int_var <42:                                                           - 30  print("Less than the answer.")                                           - 31  else:                                                                        - 32  print("Greater than the answer.")                                        - 33   - 34  for index, value inenumerate(list_var):                                     - 35  print(f"Index: {index}, Value: {value}")                                 - 36   - 37  counter =0 - 38  while counter <5:                                                           - 39  print(f"Counter value: {counter}")                                       - 40      counter +=1 - 41   - 42  squared_numbers = [x**2for x inrange(10if x %2==0]                    - 43   - 44  try:                                                                         - 45      result =10/0 - 46  except ZeroDivisionError:                                                    - 47  print("Cannot divide by zero!")                                          - 48  finally:                                                                     - 49  print("End of try-except block.")                                        - 50   - 51  classAnimal:                                                                - 52  def__init__(self, name):                                                - 53          self.name = name                                                     - 54   - 55  defspeak(self):                                                         - 56  raiseNotImplementedError("Subclasses must implement this method." - 57   - 58  classDog(Animal):                                                           - 59  defspeak(self):                                                         - 60  returnf"{self.name} says Woof!" - 61   - 62  deffibonacci(n):                                                            - 63      a, b =01 - 64  for _ inrange(n):                                                       - 65  yield a                                                              - 66          a, b = b, a + b                                                      - 67   - 68  for num infibonacci(5):                                                     - 69  print(num)                                                               - 70   - 71  withopen('test.txt''w'as f:                                             - 72      f.write("Testing with statement.")                                       - 73   - 74  @my_decorator                                                                - 75  defsay_hello():                                                             - 76  print("Hello!")                                                          - 77   - 78  say_hello()                                                                  - 79   + + + +  1  import math                                                                  +  2  from os import path  +  3   +  4  # I'm a comment :) +  5   +  6  string_var ="Hello, world!" +  7  int_var =42 +  8  float_var =3.14 +  9  complex_var =1+2j + 10   + 11  list_var = [12345 + 12  tuple_var = (12345 + 13  set_var = {12345 + 14  dict_var = {"a"1"b"2"c"3 + 15   + 16  deffunction_no_args():  + 17  return"No arguments" + 18   + 19  deffunction_with_args(a, b):  + 20  return a + b  + 21   + 22  deffunction_with_default_args(a=0, b=0):  + 23  return a * b  + 24   + 25  lambda_func =lambda x: x**2 + 26   + 27  if int_var ==42 + 28  print("It's the answer!" + 29  elif int_var <42 + 30  print("Less than the answer." + 31  else + 32  print("Greater than the answer." + 33   + 34  for index, value inenumerate(list_var):  + 35  print(f"Index: {index}, Value: {value}" + 36   + 37  counter =0 + 38  while counter <5 + 39  print(f"Counter value: {counter}" + 40      counter +=1 + 41   + 42  squared_numbers = [x**2for x inrange(10if x %2==0 + 43   + 44  try + 45      result =10/0 + 46  except ZeroDivisionError:  + 47  print("Cannot divide by zero!" + 48  finally + 49  print("End of try-except block." + 50   + 51  classAnimal + 52  def__init__(self, name):  + 53          self.name = name  + 54   + 55  defspeak(self):  + 56  raiseNotImplementedError("Subclasses must implement this method." + 57   + 58  classDog(Animal):  + 59  defspeak(self):  + 60  returnf"{self.name} says Woof!" + 61   + 62  deffibonacci(n):  + 63      a, b =01 + 64  for _ inrange(n):  + 65  yield a  + 66          a, b = b, a + b  + 67   + 68  for num infibonacci(5):  + 69  print(num)  + 70   + 71  withopen('test.txt''w'as f:  + 72      f.write("Testing with statement." + 73   + 74  @my_decorator  + 75  defsay_hello():  + 76  print("Hello!" + 77   + 78  say_hello()  + 79   @@ -34783,145 +34783,145 @@ font-weight: 700; } - .terminal-446422533-matrix { + .terminal-4033879923-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-446422533-title { + .terminal-4033879923-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-446422533-r1 { fill: #c2c2bf } - .terminal-446422533-r2 { fill: #272822 } - .terminal-446422533-r3 { fill: #f8f8f2 } - .terminal-446422533-r4 { fill: #c5c8c6 } - .terminal-446422533-r5 { fill: #90908a } - .terminal-446422533-r6 { fill: #f92672 } - .terminal-446422533-r7 { fill: #23568b } + .terminal-4033879923-r1 { fill: #c2c2bf } + .terminal-4033879923-r2 { fill: #272822 } + .terminal-4033879923-r3 { fill: #f8f8f2 } + .terminal-4033879923-r4 { fill: #c5c8c6 } + .terminal-4033879923-r5 { fill: #90908a } + .terminal-4033879923-r6 { fill: #f92672 } + .terminal-4033879923-r7 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  ^abc            # Matches any string that starts with "abc"                  -  2  abc$            # Matches any string that ends with "abc"                    -  3  ^abc$           # Matches the string "abc" and nothing else                  -  4  a.b             # Matches any string containing "a", any character, then "b" -  5  a[.]b           # Matches the string "a.b"                                   -  6  a|b             # Matches either "a" or "b"                                  -  7  a{2}            # Matches "aa"                                               -  8  a{2,}           # Matches two or more consecutive "a" characters             -  9  a{2,5}          # Matches between 2 and 5 consecutive "a" characters         - 10  a?              # Matches "a" or nothing (0 or 1 occurrence of "a") - 11  a*              # Matches zero or more consecutive "a" characters            - 12  a+              # Matches one or more consecutive "a" characters             - 13  \d              # Matches any digit (equivalent to [0-9]) - 14  \D              # Matches any non-digit                                      - 15  \w              # Matches any word character (equivalent to [a-zA-Z0-9_]) - 16  \W              # Matches any non-word character                             - 17  \s              # Matches any whitespace character (spaces, tabs, line break - 18  \S              # Matches any non-whitespace character                       - 19  (?i)abc         # Case-insensitive match for "abc"                           - 20  (?:a|b)         # Non-capturing group for either "a" or "b"                  - 21  (?<=a)b         # Positive lookbehind: matches "b" that is preceded by "a"   - 22  (?<!a)b         # Negative lookbehind: matches "b" that is not preceded by " - 23  a(?=b)          # Positive lookahead: matches "a" that is followed by "b"    - 24  a(?!b)          # Negative lookahead: matches "a" that is not followed by "b - 25   - + + + +  1  ^abc            # Matches any string that starts with "abc"                  +  2  abc$            # Matches any string that ends with "abc"  +  3  ^abc$           # Matches the string "abc" and nothing else  +  4  a.b             # Matches any string containing "a", any character, then "b" +  5  a[.]b           # Matches the string "a.b"  +  6  a|b             # Matches either "a" or "b"  +  7  a{2}            # Matches "aa"  +  8  a{2,}           # Matches two or more consecutive "a" characters  +  9  a{2,5}          # Matches between 2 and 5 consecutive "a" characters  + 10  a?              # Matches "a" or nothing (0 or 1 occurrence of "a") + 11  a*              # Matches zero or more consecutive "a" characters  + 12  a+              # Matches one or more consecutive "a" characters  + 13  \d              # Matches any digit (equivalent to [0-9]) + 14  \D              # Matches any non-digit  + 15  \w              # Matches any word character (equivalent to [a-zA-Z0-9_]) + 16  \W              # Matches any non-word character  + 17  \s              # Matches any whitespace character (spaces, tabs, line break + 18  \S              # Matches any non-whitespace character  + 19  (?i)abc         # Case-insensitive match for "abc"  + 20  (?:a|b)         # Non-capturing group for either "a" or "b"  + 21  (?<=a)b         # Positive lookbehind: matches "b" that is preceded by "a"  + 22  (?<!a)b         # Negative lookbehind: matches "b" that is not preceded by " + 23  a(?=b)          # Positive lookahead: matches "a" that is followed by "b"  + 24  a(?!b)          # Negative lookahead: matches "a" that is not followed by "b + 25   + @@ -34951,222 +34951,222 @@ font-weight: 700; } - .terminal-1861495189-matrix { + .terminal-2450386836-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1861495189-title { + .terminal-2450386836-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1861495189-r1 { fill: #c2c2bf } - .terminal-1861495189-r2 { fill: #272822 } - .terminal-1861495189-r3 { fill: #75715e } - .terminal-1861495189-r4 { fill: #f8f8f2 } - .terminal-1861495189-r5 { fill: #c5c8c6 } - .terminal-1861495189-r6 { fill: #90908a } - .terminal-1861495189-r7 { fill: #f92672 } - .terminal-1861495189-r8 { fill: #ae81ff } - .terminal-1861495189-r9 { fill: #e6db74 } + .terminal-2450386836-r1 { fill: #c2c2bf } + .terminal-2450386836-r2 { fill: #272822 } + .terminal-2450386836-r3 { fill: #75715e } + .terminal-2450386836-r4 { fill: #f8f8f2 } + .terminal-2450386836-r5 { fill: #c5c8c6 } + .terminal-2450386836-r6 { fill: #90908a } + .terminal-2450386836-r7 { fill: #f92672 } + .terminal-2450386836-r8 { fill: #ae81ff } + .terminal-2450386836-r9 { fill: #e6db74 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  -- This is a comment in SQL -  2   -  3  -- Create tables -  4  CREATETABLE Authors (                                                       -  5      AuthorID INT PRIMARY KEY,                                                -  6      Name VARCHAR(255NOT NULL,                                              -  7      Country VARCHAR(50)                                                      -  8  );                                                                           -  9   - 10  CREATETABLE Books (                                                         - 11      BookID INT PRIMARY KEY,                                                  - 12      Title VARCHAR(255NOT NULL,                                             - 13      AuthorID INT,                                                            - 14      PublishedDate DATE,                                                      - 15      FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID)                      - 16  );                                                                           - 17   - 18  -- Insert data - 19  INSERTINTO Authors (AuthorID, Name, Country) VALUES (1'George Orwell''U - 20   - 21  INSERTINTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1'1984' - 22   - 23  -- Update data - 24  UPDATE Authors SET Country ='United Kingdom'WHERE Country ='UK';          - 25   - 26  -- Select data with JOIN - 27  SELECT Books.Title, Authors.Name                                             - 28  FROM Books                                                                   - 29  JOIN Authors ON Books.AuthorID = Authors.AuthorID;                           - 30   - 31  -- Delete data (commented to preserve data for other examples) - 32  -- DELETE FROM Books WHERE BookID = 1; - 33   - 34  -- Alter table structure - 35  ALTER TABLE Authors ADD COLUMN BirthDate DATE;                               - 36   - 37  -- Create index - 38  CREATEINDEX idx_author_name ON Authors(Name);                               - 39   - 40  -- Drop index (commented to avoid actually dropping it) - 41  -- DROP INDEX idx_author_name ON Authors; - 42   - 43  -- End of script - 44   + + + +  1  -- This is a comment in SQL +  2   +  3  -- Create tables +  4  CREATETABLE Authors (  +  5      AuthorID INT PRIMARY KEY +  6      Name VARCHAR(255NOT NULL +  7      Country VARCHAR(50 +  8  );  +  9   + 10  CREATETABLE Books (  + 11      BookID INT PRIMARY KEY + 12      Title VARCHAR(255NOT NULL + 13      AuthorID INT,  + 14      PublishedDate DATE,  + 15      FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID)  + 16  );  + 17   + 18  -- Insert data + 19  INSERTINTO Authors (AuthorID, Name, Country) VALUES (1'George Orwell''U + 20   + 21  INSERTINTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1'1984' + 22   + 23  -- Update data + 24  UPDATE Authors SET Country ='United Kingdom'WHERE Country ='UK' + 25   + 26  -- Select data with JOIN + 27  SELECT Books.Title, Authors.Name   + 28  FROM Books   + 29  JOIN Authors ON Books.AuthorID = Authors.AuthorID;  + 30   + 31  -- Delete data (commented to preserve data for other examples) + 32  -- DELETE FROM Books WHERE BookID = 1; + 33   + 34  -- Alter table structure + 35  ALTER TABLE Authors ADD COLUMN BirthDate DATE;  + 36   + 37  -- Create index + 38  CREATEINDEX idx_author_name ON Authors(Name);  + 39   + 40  -- Drop index (commented to avoid actually dropping it) + 41  -- DROP INDEX idx_author_name ON Authors; + 42   + 43  -- End of script + 44   @@ -35197,151 +35197,151 @@ font-weight: 700; } - .terminal-4058290241-matrix { + .terminal-2411370561-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4058290241-title { + .terminal-2411370561-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4058290241-r1 { fill: #c2c2bf } - .terminal-4058290241-r2 { fill: #272822 } - .terminal-4058290241-r3 { fill: #75715e } - .terminal-4058290241-r4 { fill: #f8f8f2 } - .terminal-4058290241-r5 { fill: #c5c8c6 } - .terminal-4058290241-r6 { fill: #90908a } - .terminal-4058290241-r7 { fill: #f92672 } - .terminal-4058290241-r8 { fill: #e6db74 } - .terminal-4058290241-r9 { fill: #ae81ff } - .terminal-4058290241-r10 { fill: #66d9ef;font-style: italic; } + .terminal-2411370561-r1 { fill: #c2c2bf } + .terminal-2411370561-r2 { fill: #272822 } + .terminal-2411370561-r3 { fill: #75715e } + .terminal-2411370561-r4 { fill: #f8f8f2 } + .terminal-2411370561-r5 { fill: #c5c8c6 } + .terminal-2411370561-r6 { fill: #90908a } + .terminal-2411370561-r7 { fill: #f92672 } + .terminal-2411370561-r8 { fill: #e6db74 } + .terminal-2411370561-r9 { fill: #ae81ff } + .terminal-2411370561-r10 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  # This is a comment in TOML -  2   -  3  string = "Hello, world!" -  4  integer = 42 -  5  float = 3.14 -  6  boolean = true -  7  datetime = 1979-05-27T07:32:00Z -  8   -  9  fruits = ["apple""banana""cherry" - 10   - 11  [address]                               - 12  street = "123 Main St" - 13  city = "Anytown" - 14  state = "CA" - 15  zip = "12345" - 16   - 17  [person.john]                           - 18  name = "John Doe" - 19  age = 28 - 20  is_student = false - 21   - 22   - 23  [[animals]]                             - 24  name = "Fido" - 25  type = "dog" - 26   + + + +  1  # This is a comment in TOML +  2   +  3  string = "Hello, world!" +  4  integer = 42 +  5  float = 3.14 +  6  boolean = true +  7  datetime = 1979-05-27T07:32:00Z +  8   +  9  fruits = ["apple""banana""cherry" + 10   + 11  [address + 12  street = "123 Main St" + 13  city = "Anytown" + 14  state = "CA" + 15  zip = "12345" + 16   + 17  [person.john + 18  name = "John Doe" + 19  age = 28 + 20  is_student = false + 21   + 22   + 23  [[animals]]  + 24  name = "Fido" + 25  type = "dog" + 26   @@ -35372,199 +35372,199 @@ font-weight: 700; } - .terminal-4054582537-matrix { + .terminal-1896088841-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4054582537-title { + .terminal-1896088841-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4054582537-r1 { fill: #c2c2bf } - .terminal-4054582537-r2 { fill: #272822 } - .terminal-4054582537-r3 { fill: #75715e } - .terminal-4054582537-r4 { fill: #f8f8f2 } - .terminal-4054582537-r5 { fill: #c5c8c6 } - .terminal-4054582537-r6 { fill: #90908a } - .terminal-4054582537-r7 { fill: #f92672;font-weight: bold } - .terminal-4054582537-r8 { fill: #e6db74 } - .terminal-4054582537-r9 { fill: #ae81ff } - .terminal-4054582537-r10 { fill: #66d9ef;font-style: italic; } + .terminal-1896088841-r1 { fill: #c2c2bf } + .terminal-1896088841-r2 { fill: #272822 } + .terminal-1896088841-r3 { fill: #75715e } + .terminal-1896088841-r4 { fill: #f8f8f2 } + .terminal-1896088841-r5 { fill: #c5c8c6 } + .terminal-1896088841-r6 { fill: #90908a } + .terminal-1896088841-r7 { fill: #f92672;font-weight: bold } + .terminal-1896088841-r8 { fill: #e6db74 } + .terminal-1896088841-r9 { fill: #ae81ff } + .terminal-1896088841-r10 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - -  1  # This is a comment in YAML -  2   -  3  # Scalars -  4  string"Hello, world!" -  5  integer42 -  6  float3.14 -  7  booleantrue -  8   -  9  # Sequences (Arrays) - 10  fruits:                                               - 11    - Apple - 12    - Banana - 13    - Cherry - 14   - 15  # Nested sequences - 16  persons:                                              - 17    - nameJohn - 18  age28 - 19  is_studentfalse - 20    - nameJane - 21  age22 - 22  is_studenttrue - 23   - 24  # Mappings (Dictionaries) - 25  address:                                              - 26  street123 Main St - 27  cityAnytown - 28  stateCA - 29  zip'12345' - 30   - 31  # Multiline string - 32  description|                                        - 33    This is a multiline                                 - 34    string in YAML. - 35   - 36  # Inline and nested collections - 37  colors: { redFF0000green00FF00blue0000FF }  - 38   + + + +  1  # This is a comment in YAML +  2   +  3  # Scalars +  4  string"Hello, world!" +  5  integer42 +  6  float3.14 +  7  booleantrue +  8   +  9  # Sequences (Arrays) + 10  fruits + 11    - Apple + 12    - Banana + 13    - Cherry + 14   + 15  # Nested sequences + 16  persons + 17    - nameJohn + 18  age28 + 19  is_studentfalse + 20    - nameJane + 21  age22 + 22  is_studenttrue + 23   + 24  # Mappings (Dictionaries) + 25  address + 26  street123 Main St + 27  cityAnytown + 28  stateCA + 29  zip'12345' + 30   + 31  # Multiline string + 32  description + 33    This is a multiline  + 34    string in YAML. + 35   + 36  # Inline and nested collections + 37  colors: { redFF0000green00FF00blue0000FF }  + 38   @@ -35595,58 +35595,58 @@ font-weight: 700; } - .terminal-1544890574-matrix { + .terminal-4247136462-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1544890574-title { + .terminal-4247136462-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1544890574-r1 { fill: #f8f8f2 } - .terminal-1544890574-r2 { fill: #c5c8c6 } - .terminal-1544890574-r3 { fill: #65686a } - .terminal-1544890574-r4 { fill: #272822 } + .terminal-4247136462-r1 { fill: #f8f8f2 } + .terminal-4247136462-r2 { fill: #c5c8c6 } + .terminal-4247136462-r3 { fill: #65686a } + .terminal-4247136462-r4 { fill: #272822 } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line. - ▌                     - I am another line.             - - I am the final line.  + + + + I am a line. + + I am another line.             + + I am the final line.  @@ -35676,58 +35676,58 @@ font-weight: 700; } - .terminal-1447338365-matrix { + .terminal-34578813-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1447338365-title { + .terminal-34578813-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1447338365-r1 { fill: #f8f8f2 } - .terminal-1447338365-r2 { fill: #272822 } - .terminal-1447338365-r3 { fill: #c5c8c6 } - .terminal-1447338365-r4 { fill: #65686a } + .terminal-34578813-r1 { fill: #f8f8f2 } + .terminal-34578813-r2 { fill: #272822 } + .terminal-34578813-r3 { fill: #c5c8c6 } + .terminal-34578813-r4 { fill: #65686a } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line. - ▌                     - I am another line.    - - I am the final line.  + + + + I am a line. + + I am another line.  + + I am the final line.  @@ -35757,58 +35757,58 @@ font-weight: 700; } - .terminal-4280380793-matrix { + .terminal-2707713401-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4280380793-title { + .terminal-2707713401-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4280380793-r1 { fill: #f8f8f2 } - .terminal-4280380793-r2 { fill: #272822 } - .terminal-4280380793-r3 { fill: #c5c8c6 } - .terminal-4280380793-r4 { fill: #65686a } + .terminal-2707713401-r1 { fill: #f8f8f2 } + .terminal-2707713401-r2 { fill: #272822 } + .terminal-2707713401-r3 { fill: #c5c8c6 } + .terminal-2707713401-r4 { fill: #65686a } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line. - ▌                     - I am another line. - ▌                     - I am the final line.  + + + + I am a line. + + I am another line. + + I am the final line.  @@ -35838,58 +35838,58 @@ font-weight: 700; } - .terminal-154203323-matrix { + .terminal-4068209851-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-154203323-title { + .terminal-4068209851-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-154203323-r1 { fill: #f8f8f2 } - .terminal-154203323-r2 { fill: #c5c8c6 } - .terminal-154203323-r3 { fill: #65686a } - .terminal-154203323-r4 { fill: #272822 } + .terminal-4068209851-r1 { fill: #f8f8f2 } + .terminal-4068209851-r2 { fill: #c5c8c6 } + .terminal-4068209851-r3 { fill: #65686a } + .terminal-4068209851-r4 { fill: #272822 } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line. - ▌                     - I am another line. - ▌                     - I am the final line. + + + + I am a line. + + I am another line. + + I am the final line. @@ -35919,57 +35919,57 @@ font-weight: 700; } - .terminal-2079201489-matrix { + .terminal-1184307409-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2079201489-title { + .terminal-1184307409-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2079201489-r1 { fill: #f8f8f2 } - .terminal-2079201489-r2 { fill: #c5c8c6 } - .terminal-2079201489-r3 { fill: #272822 } + .terminal-1184307409-r1 { fill: #f8f8f2 } + .terminal-1184307409-r2 { fill: #c5c8c6 } + .terminal-1184307409-r3 { fill: #272822 } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line.          - - I am another line.    - - I am the final line.  + + + + I am a line.  + + I am another line.  + + I am the final line.  @@ -35999,57 +35999,57 @@ font-weight: 700; } - .terminal-2869124204-matrix { + .terminal-1437293676-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2869124204-title { + .terminal-1437293676-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2869124204-r1 { fill: #f8f8f2 } - .terminal-2869124204-r2 { fill: #c5c8c6 } - .terminal-2869124204-r3 { fill: #272822 } + .terminal-1437293676-r1 { fill: #f8f8f2 } + .terminal-1437293676-r2 { fill: #c5c8c6 } + .terminal-1437293676-r3 { fill: #272822 } - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - I am a line.          - - I am another line.             - - I am the final line.  + + + + I am a line.  + + I am another line.             + + I am the final line.  @@ -36079,70 +36079,70 @@ font-weight: 700; } - .terminal-3703018469-matrix { + .terminal-3489174501-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3703018469-title { + .terminal-3489174501-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3703018469-r1 { fill: #6272a4 } - .terminal-3703018469-r2 { fill: #ff79c6 } - .terminal-3703018469-r3 { fill: #f8f8f2 } - .terminal-3703018469-r4 { fill: #50fa7b } - .terminal-3703018469-r5 { fill: #c5c8c6 } - .terminal-3703018469-r6 { fill: #c2c2bf;font-weight: bold } - .terminal-3703018469-r7 { fill: #bd93f9 } - .terminal-3703018469-r8 { fill: #282a36 } - .terminal-3703018469-r9 { fill: #f1fa8c } + .terminal-3489174501-r1 { fill: #6272a4 } + .terminal-3489174501-r2 { fill: #ff79c6 } + .terminal-3489174501-r3 { fill: #f8f8f2 } + .terminal-3489174501-r4 { fill: #50fa7b } + .terminal-3489174501-r5 { fill: #c5c8c6 } + .terminal-3489174501-r6 { fill: #c2c2bf;font-weight: bold } + .terminal-3489174501-r7 { fill: #bd93f9 } + .terminal-3489174501-r8 { fill: #282a36 } + .terminal-3489174501-r9 { fill: #f1fa8c } - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - 1  defhello(name): - 2      x =123 - 3  whilenotFalse:            - 4  print("hello "+ name)  - 5  continue - 6   + + + + 1  defhello(name): + 2      x =123 + 3  whilenotFalse + 4  print("hello "+ name)  + 5  continue + 6   @@ -36173,73 +36173,73 @@ font-weight: 700; } - .terminal-2524066112-matrix { + .terminal-1989357888-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2524066112-title { + .terminal-1989357888-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2524066112-r1 { fill: #bbbbbb } - .terminal-2524066112-r2 { fill: #cf222e } - .terminal-2524066112-r3 { fill: #24292e } - .terminal-2524066112-r4 { fill: #6639bb } - .terminal-2524066112-r5 { fill: #c5c8c6 } - .terminal-2524066112-r6 { fill: #a4a4a4 } - .terminal-2524066112-r7 { fill: #e36209 } - .terminal-2524066112-r8 { fill: #0450ae } - .terminal-2524066112-r9 { fill: #d73a49 } - .terminal-2524066112-r10 { fill: #fafbfc } - .terminal-2524066112-r11 { fill: #7daf9c } - .terminal-2524066112-r12 { fill: #093069 } + .terminal-1989357888-r1 { fill: #bbbbbb } + .terminal-1989357888-r2 { fill: #cf222e } + .terminal-1989357888-r3 { fill: #24292e } + .terminal-1989357888-r4 { fill: #6639bb } + .terminal-1989357888-r5 { fill: #c5c8c6 } + .terminal-1989357888-r6 { fill: #a4a4a4 } + .terminal-1989357888-r7 { fill: #e36209 } + .terminal-1989357888-r8 { fill: #0450ae } + .terminal-1989357888-r9 { fill: #d73a49 } + .terminal-1989357888-r10 { fill: #fafbfc } + .terminal-1989357888-r11 { fill: #7daf9c } + .terminal-1989357888-r12 { fill: #093069 } - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - 1  defhello(name): - 2  x=123 - 3  whilenotFalse:            - 4  print("hello "+name - 5  continue - 6   + + + + 1  defhello(name): + 2  x=123 + 3  whilenotFalse + 4  print("hello "+name + 5  continue + 6   @@ -36270,71 +36270,71 @@ font-weight: 700; } - .terminal-3063516098-matrix { + .terminal-2466417602-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3063516098-title { + .terminal-2466417602-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3063516098-r1 { fill: #90908a } - .terminal-3063516098-r2 { fill: #f92672 } - .terminal-3063516098-r3 { fill: #f8f8f2 } - .terminal-3063516098-r4 { fill: #a6e22e } - .terminal-3063516098-r5 { fill: #c5c8c6 } - .terminal-3063516098-r6 { fill: #c2c2bf } - .terminal-3063516098-r7 { fill: #ae81ff } - .terminal-3063516098-r8 { fill: #272822 } - .terminal-3063516098-r9 { fill: #66d9ef;font-style: italic; } - .terminal-3063516098-r10 { fill: #e6db74 } + .terminal-2466417602-r1 { fill: #90908a } + .terminal-2466417602-r2 { fill: #f92672 } + .terminal-2466417602-r3 { fill: #f8f8f2 } + .terminal-2466417602-r4 { fill: #a6e22e } + .terminal-2466417602-r5 { fill: #c5c8c6 } + .terminal-2466417602-r6 { fill: #c2c2bf } + .terminal-2466417602-r7 { fill: #ae81ff } + .terminal-2466417602-r8 { fill: #272822 } + .terminal-2466417602-r9 { fill: #66d9ef;font-style: italic; } + .terminal-2466417602-r10 { fill: #e6db74 } - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - 1  defhello(name): - 2      x =123 - 3  whilenotFalse:            - 4  print("hello "+ name)  - 5  continue - 6   + + + + 1  defhello(name): + 2      x =123 + 3  whilenotFalse + 4  print("hello "+ name)  + 5  continue + 6   @@ -36365,70 +36365,70 @@ font-weight: 700; } - .terminal-2963185736-matrix { + .terminal-3431178312-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2963185736-title { + .terminal-3431178312-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2963185736-r1 { fill: #6e7681 } - .terminal-2963185736-r2 { fill: #569cd6 } - .terminal-2963185736-r3 { fill: #cccccc } - .terminal-2963185736-r4 { fill: #4ec9b0 } - .terminal-2963185736-r5 { fill: #c5c8c6 } - .terminal-2963185736-r6 { fill: #b5cea8 } - .terminal-2963185736-r7 { fill: #1e1e1e } - .terminal-2963185736-r8 { fill: #7daf9c } - .terminal-2963185736-r9 { fill: #ce9178 } + .terminal-3431178312-r1 { fill: #6e7681 } + .terminal-3431178312-r2 { fill: #569cd6 } + .terminal-3431178312-r3 { fill: #cccccc } + .terminal-3431178312-r4 { fill: #4ec9b0 } + .terminal-3431178312-r5 { fill: #c5c8c6 } + .terminal-3431178312-r6 { fill: #b5cea8 } + .terminal-3431178312-r7 { fill: #1e1e1e } + .terminal-3431178312-r8 { fill: #7daf9c } + .terminal-3431178312-r9 { fill: #ce9178 } - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - 1  defhello(name): - 2      x =123 - 3  whilenotFalse:            - 4  print("hello "+ name)  - 5  continue - 6   + + + + 1  defhello(name): + 2      x =123 + 3  whilenotFalse + 4  print("hello "+ name)  + 5  continue + 6   From d4c82e260c9d942efac5fe8cb13cf63b6a3638b3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 12:56:47 +0000 Subject: [PATCH 124/150] Updating chunk and divide_line tests using to reflect code changes --- tests/test_wrap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_wrap.py b/tests/test_wrap.py index 5a5d2b59f9..67de871511 100644 --- a/tests/test_wrap.py +++ b/tests/test_wrap.py @@ -10,10 +10,10 @@ (" ", [(0, 4, " ")]), ("\t", [(0, 1, "\t")]), ("foo", [(0, 3, "foo")]), - (" foo ", [(0, 7, " foo ")]), + (" foo ", [(0, 2, " "), (2, 7, "foo ")]), ("foo bar", [(0, 4, "foo "), (4, 7, "bar")]), - ("\tfoo bar", [(0, 5, "\tfoo "), (5, 8, "bar")]), - (" foo bar", [(0, 5, " foo "), (5, 8, "bar")]), + ("\tfoo bar", [(0, 1, "\t"), (1, 5, "foo "), (5, 8, "bar")]), + (" foo bar", [(0, 1, " "), (1, 5, "foo "), (5, 8, "bar")]), ("foo bar ", [(0, 4, "foo "), (4, 10, "bar ")]), ("foo\t bar ", [(0, 6, "foo\t "), (6, 12, "bar ")]), ("木\t 川 ", [(0, 4, "木\t "), (4, 8, "川 ")]), @@ -30,8 +30,8 @@ def test_chunks(input_text, expected_output): ("\t", 6, 4, []), (" ", 6, 4, []), ("foo bar baz", 6, 4, [4, 8]), - ("\tfoo bar baz", 6, 4, [3, 9]), - ("\tfo bar baz", 6, 4, [3, 8]), + ("\tfoo bar baz", 6, 4, [1, 5, 9]), + ("\tfo bar baz", 6, 4, [1, 4, 8]), ("\tfo bar baz", 6, 8, [1, 4, 8]), ("\tfo bar baz\t", 6, 8, [1, 4, 8]), ("\t\t\tfo bar baz\t", 20, 4, [10]), @@ -39,5 +39,5 @@ def test_chunks(input_text, expected_output): ("\t\t\t\t\t", 19, 4, [4]), ], ) -def test_divide_line(text, width, tab_size, expected_output): +def test_compute_wrap_offsets(text, width, tab_size, expected_output): assert compute_wrap_offsets(text, width, tab_size) == expected_output From 7bfbaa119b1e4373b2672885784317095e122b25 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 12:59:20 +0000 Subject: [PATCH 125/150] We dont need always update on selection reactive --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 2dbbb41b38..457b503a16 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -201,7 +201,7 @@ class TextArea(ScrollView, can_focus=True): """ selection: Reactive[Selection] = reactive( - Selection(), always_update=True, init=False + Selection(), always_update=False, init=False ) """The selection start and end locations (zero-based line_index, offset). From b6a0ab57eef403676a465fe5d1a689c142d1452a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 12:59:43 +0000 Subject: [PATCH 126/150] Formatting --- src/textual/widgets/_text_area.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 457b503a16..bd63b5455b 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -200,9 +200,7 @@ class TextArea(ScrollView, can_focus=True): Syntax highlighting is only possible when the `language` attribute is set. """ - selection: Reactive[Selection] = reactive( - Selection(), always_update=False, init=False - ) + selection: Reactive[Selection] = reactive(Selection(), init=False) """The selection start and end locations (zero-based line_index, offset). This represents the cursor location and the current selection. From 7c967dc4c936ef498029ad37125f70d26ebb8f0a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:10:25 +0000 Subject: [PATCH 127/150] Delete to line start should not use "smart home" behaviour --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index bd63b5455b..8ea2e98ab7 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1734,7 +1734,7 @@ def action_delete_line(self) -> None: def action_delete_to_start_of_line(self) -> None: """Deletes from the cursor location to the start of the line.""" from_location = self.selection.end - to_location = self.get_cursor_line_start_location(smart_home=True) + to_location = self.get_cursor_line_start_location() self.delete(from_location, to_location, maintain_selection_offset=False) def action_delete_to_end_of_line(self) -> None: From 29a93338004800c5c6c94fa30144eaed4e4a828f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:14:56 +0000 Subject: [PATCH 128/150] Only send TextArea SelectionChanged message when the selection actually changes --- src/textual/widgets/_text_area.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 8ea2e98ab7..3d17822485 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -200,7 +200,9 @@ class TextArea(ScrollView, can_focus=True): Syntax highlighting is only possible when the `language` attribute is set. """ - selection: Reactive[Selection] = reactive(Selection(), init=False) + selection: Reactive[Selection] = reactive( + Selection(), init=False, always_update=True + ) """The selection start and end locations (zero-based line_index, offset). This represents the cursor location and the current selection. @@ -402,7 +404,9 @@ def _build_highlight_map(self) -> None: # Add the last line of the node range highlights[node_end_row].append((0, node_end_column, highlight_name)) - def _watch_selection(self, selection: Selection) -> None: + def _watch_selection( + self, previous_selection: Selection, selection: Selection + ) -> None: """When the cursor moves, scroll it into view.""" # Find the visual offset of the cursor in the document cursor_location = selection.end @@ -425,7 +429,8 @@ def _watch_selection(self, selection: Selection) -> None: self.refresh_lines(match_row) self.app.cursor_position = self.cursor_screen_offset - self.post_message(self.SelectionChanged(selection, self)) + if previous_selection != selection: + self.post_message(self.SelectionChanged(selection, self)) def _recompute_cursor_offset(self): """Recompute the (x, y) coordinate of the cursor in the wrapped document.""" From 43ecfb66efee375dbab1b2b502dc3bb24d8d5b72 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:18:35 +0000 Subject: [PATCH 129/150] Dependency update --- poetry.lock | 792 +++++++++++++++++++++++++++------------------------- 1 file changed, 413 insertions(+), 379 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5a99d9266c..196df5d5c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -112,19 +112,20 @@ frozenlist = ">=1.1.0" [[package]] name = "anyio" -version = "4.1.0" +version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.1.0-py3-none-any.whl", hash = "sha256:56a415fbc462291813a94528a779597226619c8e78af7de0507333f700011e5f"}, - {file = "anyio-4.1.0.tar.gz", hash = "sha256:5a0bec7085176715be77df87fc66d6c9d70626bd752fcc85f57cdbee5b3760da"}, + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] @@ -144,69 +145,69 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "babel" -version = "2.13.1" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} -setuptools = {version = "*", markers = "python_version >= \"3.12\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "23.12.0" +version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, - {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, - {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, - {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, - {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, - {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, - {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, - {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, - {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, - {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, - {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, - {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, - {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"}, - {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"}, - {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"}, - {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"}, - {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"}, - {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"}, - {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"}, - {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"}, - {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, - {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -382,63 +383,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, + {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, + {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, + {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, + {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, + {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, + {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, + {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, + {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, + {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, + {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, + {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, + {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, ] [package.extras] @@ -487,72 +488,88 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "frozenlist" -version = "1.4.0" +version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] @@ -588,20 +605,20 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.40" +version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] [[package]] name = "griffe" @@ -699,13 +716,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.0" +version = "7.0.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.0-py3-none-any.whl", hash = "sha256:d97503976bb81f40a193d41ee6570868479c69d5068651eb039c40d850c59d67"}, - {file = "importlib_metadata-7.0.0.tar.gz", hash = "sha256:7fc841f8b8332803464e5dc1c63a2e59121f46ca186c0e2e182e80bf8c1319f7"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [package.dependencies] @@ -729,13 +746,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -766,13 +783,13 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "markdown" -version = "3.5.1" +version = "3.5.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, - {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, + {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"}, + {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"}, ] [package.dependencies] @@ -979,13 +996,13 @@ mkdocs = "*" [[package]] name = "mkdocs-material" -version = "9.5.2" +version = "9.5.4" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.2-py3-none-any.whl", hash = "sha256:6ed0fbf4682491766f0ec1acc955db6901c2fd424c7ab343964ef51b819741f5"}, - {file = "mkdocs_material-9.5.2.tar.gz", hash = "sha256:ca8b9cd2b3be53e858e5a1a45ac9668bd78d95d77a30288bb5ebc1a31db6184c"}, + {file = "mkdocs_material-9.5.4-py3-none-any.whl", hash = "sha256:efd7cc8ae03296d728da9bd38f4db8b07ab61f9738a0cbd0dfaf2a15a50e7343"}, + {file = "mkdocs_material-9.5.4.tar.gz", hash = "sha256:3d196ee67fad16b2df1a458d650a8ac1890294eaae368d26cee71bc24ad41c40"}, ] [package.dependencies] @@ -993,7 +1010,7 @@ babel = ">=2.10,<3.0" colorama = ">=0.4,<1.0" jinja2 = ">=3.0,<4.0" markdown = ">=3.2,<4.0" -mkdocs = ">=1.5.3,<2.0" +mkdocs = ">=1.5.3,<1.6.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pygments = ">=2.16,<3.0" @@ -1019,13 +1036,13 @@ files = [ [[package]] name = "mkdocs-rss-plugin" -version = "1.9.0" +version = "1.12.0" description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." optional = false python-versions = ">=3.8, <4" files = [ - {file = "mkdocs-rss-plugin-1.9.0.tar.gz", hash = "sha256:eeb576945d3d9990cdf8aa3545062669892ea4410e5a960072d44cec867dba42"}, - {file = "mkdocs_rss_plugin-1.9.0-py2.py3-none-any.whl", hash = "sha256:8c3eda30ec59e6b51c6c0ed2b27e6f5c907583d8828122c81140b4505f42b72c"}, + {file = "mkdocs-rss-plugin-1.12.0.tar.gz", hash = "sha256:81ccf8d328539e59960b08466a7aaefc5937b4747534e624166429e4a274dcca"}, + {file = "mkdocs_rss_plugin-1.12.0-py2.py3-none-any.whl", hash = "sha256:e2150df0765ce077bdbf884964fbd04dce78c564e42e5d13c2046e394736965c"}, ] [package.dependencies] @@ -1036,8 +1053,8 @@ tzdata = {version = "==2023.*", markers = "python_version >= \"3.9\" and sys_pla [package.extras] dev = ["black", "flake8 (>=6,<7)", "flake8-bugbear (>=23.12)", "flake8-builtins (>=2.1)", "flake8-eradicate (>=1)", "flake8-isort (>=6)", "pre-commit (>=3,<4)"] -doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (==0.7.*)", "pygments (>=2.5,<3)", "pymdown-extensions (>=10,<11)"] -test = ["feedparser (>=6.0,<6.1)", "mkdocs-material (>=9)", "pytest-cov (>=4,<4.2)", "validator-collection (>=1.5,<1.6)"] +doc = ["mkdocs-git-committers-plugin-2 (>=1.2,<2.3)", "mkdocs-git-revision-date-localized-plugin (>=1,<1.3)", "mkdocs-material[imaging] (>=9.5.1,<10)", "mkdocs-minify-plugin (==0.7.*)", "mkdocstrings[python] (>=0.18,<1)", "termynal (>=0.11.1,<0.12)"] +test = ["feedparser (>=6.0.11,<6.1)", "jsonfeed-util (>=1.1.2,<2)", "mkdocs-material[imaging] (>=9)", "pytest-cov (>=4,<4.2)", "validator-collection (>=1.5,<1.6)"] [[package]] name = "mkdocstrings" @@ -1229,38 +1246,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] @@ -1396,13 +1413,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.5" +version = "10.7" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.5-py3-none-any.whl", hash = "sha256:1f0ca8bb5beff091315f793ee17683bc1390731f6ac4c5eb01e27464b80fe879"}, - {file = "pymdown_extensions-10.5.tar.gz", hash = "sha256:1b60f1e462adbec5a1ed79dac91f666c9c0d241fa294de1989f29d20096cfd0b"}, + {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"}, + {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"}, ] [package.dependencies] @@ -1414,13 +1431,13 @@ extra = ["pygments (>=2.12)"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -1436,13 +1453,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.2" +version = "0.23.3" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.2.tar.gz", hash = "sha256:c16052382554c7b22d48782ab3438d5b10f8cf7a4bdcae7f0f67f097d95beecc"}, - {file = "pytest_asyncio-0.23.2-py3-none-any.whl", hash = "sha256:ea9021364e32d58f0be43b91c6233fb8d2224ccef2398d6837559e587682808f"}, + {file = "pytest-asyncio-0.23.3.tar.gz", hash = "sha256:af313ce900a62fbe2b1aed18e37ad757f1ef9940c6b6a88e2954de38d6b1fb9f"}, + {file = "pytest_asyncio-0.23.3-py3-none-any.whl", hash = "sha256:37a9d912e8338ee7b4a3e917381d1c95bfc8682048cb0fbc35baba316ec1faba"}, ] [package.dependencies] @@ -1589,99 +1606,104 @@ pyyaml = "*" [[package]] name = "regex" -version = "2023.10.3" +version = "2023.12.25" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.7" files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, ] [[package]] @@ -1728,28 +1750,30 @@ version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, -] +files = [] +develop = true [package.dependencies] markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} +pygments = "^2.13.0" +typing-extensions = {version = ">=4.0.0, <5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[package.source] +type = "directory" +url = "../rich" + [[package]] name = "setuptools" -version = "69.0.2" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, - {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] @@ -1807,20 +1831,20 @@ pytest = ">=5.1.0,<8.0.0" [[package]] name = "textual-dev" -version = "1.2.1" +version = "1.4.0" description = "Development tools for working with Textual" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "textual_dev-1.2.1-py3-none-any.whl", hash = "sha256:a96ff43841cadf853dd689d68c2fc920a23ad71cfa9a33917ca53e96d1cc81f3"}, - {file = "textual_dev-1.2.1.tar.gz", hash = "sha256:0bda11adfc541e0cc9e49bdf37a8b852281dc2387bb6ff3d01f40c7a3f841684"}, + {file = "textual_dev-1.4.0-py3-none-any.whl", hash = "sha256:330beec18b8f469adf7cdf9d69cc94965bf3e69d9aec23625d62cdedcadab044"}, + {file = "textual_dev-1.4.0.tar.gz", hash = "sha256:a20ea746a93e66978e9dfe71a7e5409854c96cc3e46550cc40760b199d7a5d3b"}, ] [package.dependencies] aiohttp = ">=3.8.1" click = ">=8.1.2" msgpack = ">=1.0.3" -textual = ">=0.33.0" +textual = ">=0.36.0" typing-extensions = ">=4.4.0,<5.0.0" [[package]] @@ -2003,69 +2027,79 @@ setuptools = {version = ">=60.0.0", markers = "python_version >= \"3.12\""} [[package]] name = "tree-sitter-languages" -version = "1.8.0" +version = "1.9.1" description = "Binary Python wheels for all tree sitter languages." optional = true python-versions = "*" files = [ - {file = "tree_sitter_languages-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a20045f0c7a8394ac0c085c3a7da88438f9e62c6a8b661ebf63c3edb8c3f2bf6"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ef80d5896b420d434f7322abbc2c5a5548a37b3821c5486ed0612d2bd760d5a"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e7c7100c7b4a364035417e811ab8d43c8ee4e38d0c6ab9cad9c4d8133c0abd"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9618bfb5874c43fcb4da43cd71bc24f01f4f94cd55bb9923c4210c7f9e977eb5"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7b0b606be0c61155bde8e913528b7dc038e8476891f5b198996f780c678ecc0"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:306b49d60afb8c08f95a55e38744687521aa9350a97e9d6d1512db47ea401c51"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b561b979d1dc15a0b2bc35586fe4ccf95049812944042ea5760d8450b63c3fe0"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c46c82a5649c41fd4ce7483534fe548a98af6ef6490b5c9f066e2df43e40aa9"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-win32.whl", hash = "sha256:4d84b2bf63f8dc51188f83a6dfc7d70365e1c720310c1222f44d0cd2ec76e4d0"}, - {file = "tree_sitter_languages-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:c59b81123fa73e7d66d3a8bc0e64af2f2a8fcbbce1b08676d9188ec5edb4fb49"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a5816a1e394d717a86b9f5cbb0af08ad92a9badbb4b95678d75052e6bd7402"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:912a12a56361077715b231f1931cf7d472f7d6cfdc76abb806e6b1bdf11d3835"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33838baa8583b2c9f9df4d672237158dcc9d845782413569b51cc8dfed2fb4de"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b6f148e459e8af180be68e9f9c8f8c4db0db170850482b083fd078fba3f4076"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96dbdaff9d317d193451bc5b566098717096381d67674f9e65fb8f0ebe98c847"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c719535ebdd39f94c26f2182b0d16c45a2996b03b5ad7b78a863178eca1546d"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d5c4cb2f4231135d038155787c96f4ecdf44f63eeee8d9e36b100b96a80a7764"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:524bfa0bcbf0fe8cbb93712336d1de0a3073f08c004bb920270d69c0c3eaaf14"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-win32.whl", hash = "sha256:26a0b923c47eeed551e4c307b7badb337564523cca36f9c40e188a308f471c72"}, - {file = "tree_sitter_languages-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3f0ed6297878f9f335f652843e9ab48c561f9a5b312a41a868b5fc127567447b"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0f18d0d98b92bfa40ec15fc4cc5eb5e1f39b9f2f8986cf4cb3e1f8a8e31b06cf"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c742b0733be6d057d323252c56b8419fa2e120510baf601f710363971ae99ae7"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4417710db978edf6bad1e1e59efba04693919ed45c4115bae7da359354d9d8af"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a051e1cceddd1126ce0fa0d3faa12873e5b52cafae0893cc82d22b21348fc83c"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2665768f7ef6d00ab3847c5a3a5fdd54fbc62a9abf80475bff26dcc7a4e8544f"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76be6fd0d1e514e496eb3430b05ce0efd2f7d09fc3dfe47cc99afc653313c36a"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:510c5ba5dd3ce502f2963c46cc56ad4a0acd1b776be9b119da03f392bda9f8bf"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-win32.whl", hash = "sha256:f852ff7b77df5c7a3f8b825c31673aee59456a93347b58cfa43fdda81fe1cb63"}, - {file = "tree_sitter_languages-1.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:53934c8b09650e576ad5724b84c6891d84b69508ad71a78bb2d4dc88b63543fc"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:400ba190fd08cec9412d70efa09e2f1791a0db82a3e9b31f677e145ad2e48a9a"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:937b0e8cc07fb6574b475fcaded8dd16fa445c66f40bf449b4e50684fd8c380b"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c165c5d13ee335c74a2b6dc6edfcf85045839fa2f7254d2aae3ae9f76020e87d"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124117c6184653cdd381c70a16e5d6a45a41c3f6470d9d756452ea50aa6bb472"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4c12232c93d4c5c8b3b6324850085971fa93c2226842778f07fe3fba9a7683c1"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9baf99c00366fe2c8e61bf7489d86eaab4c884f669abdb30ba2450cfabb77f7"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f97baf3d574fc44872c1de8c941888c940a0376c8f80a15ec6931d19b4fe2091"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:c40267904f734d8a7e9a05ce60f04ea95db59cad183207c4af34e6bc1f5bbd1f"}, - {file = "tree_sitter_languages-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:06b8d11ea550d3c4f0ce0774d6b521c44f2e83d1a77d50f85bea3ed150e66c28"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a151d4f2637309f1780b9a0422cdeea3c0a8a6209800f587fe4374ebe13e6a1"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a3afb35a316495ff1b848aadeb4f9f7ef6522e9b730a7a35cfe28361398404e"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22eb91d745b96936c13fc1c100d78e6dcbaa14e9fbe54e180cdc6ca1b262c0f"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54a3a83474d3abb44a178aa1f0a5ef73002c014e7e489977fd39624c1ac0a476"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a13aa1e6f0fc76268e8fed282fb433ca4b8f6644bb75476a10d28cc19d6cf3"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68872fcea16f7ddbfeec52120b7070e18a820407d16f6b513ec95ede4110df82"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:43928c43d8a25204297c43bbaab0c4b567a7e85901a19ef9317a3964ad8eb76e"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cca84cacd5530f23ae5d05e4904c2d42f7479fd80541eda34c27cadbf9611d6b"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-win32.whl", hash = "sha256:9d043fdbaf260d0f36f8843acf43096765bed913be71ad705265dccb8e381e1c"}, - {file = "tree_sitter_languages-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f5bbccf1250dc07e74fd86f08a9ed614efd64986a48c142846cd21e84267d46b"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10046058a4213304e3ba78a52ab88d8d5a2703f5d193e7e976d0a53c2fa12f4b"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fc84bb37ca0bb1f45f808a38733f6bb9c2e8fc8a02712fe8658fe3d31ed74e7"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b13199282d71d2a841f404f58ccf914b3917b27a99917b0a79b80c93f8a24e"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94f5f5ac57591004823385bd7f4cc1b62c7b0b08efc1c39a5e33fb2f8c201bf"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a796a359bd6fb4f2b67e29f86c9130bd6ae840d75d31d356594f92d5505f43d"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:45a6edf0106ff653940fe52fb8a47f8c03d0c5981312ac036888d44102840452"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f077fe6099bb310a247514b68d7103c6dbafef552856fcd225d0867f78b620b7"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3842ef8d05e3368c227fd5a57f08f636374b4b870070916d08c4aafb99d04cd1"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-win32.whl", hash = "sha256:3e9eafc7079114783b5385a769fd190c93525bcae3cf6791fd819c617067394e"}, - {file = "tree_sitter_languages-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:9d30b7f48f18a60eea9a0f9494e0f0ea6f560d861770a84c3faab8d7a446fc55"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5dee458cf1bd1e725470949124e24db842dc789039ea7ff5ba46b338e5f0dc60"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81921135fa15469586b1528088f78553e60a900d3045f4f37021ad3836219216"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edd60780d14c727179acb7bb48fbe4f79da9b830abdeb0d12c06a9f2c37928c7"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a28da3f60a6bc23195d6850836e477c149d4aaf58cdb0eb662741dca4f6401e2"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9778c00a58ee77006abc5af905b591551b158ce106c8cc6c3b4148d624ccabf"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f68cfec0d74d6344db9c83414f401dcfc753916e71fac7d37f3a5e35b79e5ec"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:02142d81b2cd759b5fe246d403e4fba80b70268d108bd2b108301e64a84437a6"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ca4e0041c2ead2a8b354b9c229faee152bfd4617480c85cf2b352acf459db3cc"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-win32.whl", hash = "sha256:506ff5c3646e7b3a533f9e925221d4fe63b88dad0b7ffc1fb96db4c271994606"}, + {file = "tree_sitter_languages-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ac3899e05f2bf0a7c8da70ef5a077ab3dbd442f99eb7452aabbe67bc7b29ddf"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:823426c3768eea88b6a4fd70dc668b72de90cc9f44d041a579c76d024d7d0697"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51f64b11f30cef3c5c9741e06221a46948f7c82d53ea2468139028eaf4858cca"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1c384bcd2695ebf873bc63eccfa0b9e1c3c944cd6a6ebdd1139a2528d2d6f"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fecf8553645fc1ad84921e97b03615d84aca22c35d020f629bb44cb6a28a302e"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1a499004189bf9f338f3412d4c1c05a643e86d4619a60ba4b3ae56bc4bf5db9"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:634ef22744b4af2ed9a43fea8309ec1171b062e37c609c3463364c790a08dae3"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9394eb34208abcfa9c26ece39778037a8d97da3ef59501185303fef0ab850290"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221c367be0129da540fbb84170e18c5b8c56c09fd2f6143e116eebbef72c780e"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-win32.whl", hash = "sha256:15d03f54f913f47ac36277d8a521cd425415a25b020e0845d7b8843f5f5e1209"}, + {file = "tree_sitter_languages-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:7c565c18cebc72417ebc8f0f4cd5cb91dda51874164045cc274f47c913b194aa"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cde380cdc37594e7fcbade6a4b396dbeab52a1cecfe884cd814e1a1541ca6b93"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9c4f2409e5460bdec5921ee445f748ea7c319469e347a13373e3c7086dbf0315"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a17bbe91a78a29a9c14ab8bb07ed3761bb2708b58815bafc02d0965b15cb99e5"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:369402e2b395de2655d769e515401fe7c7df247a83aa28a6362e808b8a017fae"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4a382d1e463e6ae60bbbd0c1f3db48e83b3c1a3af98d652af11de4c0e6171fc"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc60fb35f377143b30f4319fbaac0503b12cfb49de34082a479c7f0cc28927f1"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e953fb43767e327bf5c1d0585ee39236eaff47683cbda2811cbe0227fd41ad7"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c5a6df25eae23a5e2d448218b130207476cb8a613ac40570d49008243b0915bb"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-win32.whl", hash = "sha256:2720f9a639f5d5c17692135f3f2d60506c240699d0c1becdb895546e553f2339"}, + {file = "tree_sitter_languages-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:f19157c33ddc1e75ae7843b813e65575ed2040e1638643251bd603bb0f52046b"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:40880b5e774c3d5759b726273c36f83042d39c600c3aeefaf39248c3adec92d0"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad71366ee2458bda6df5a7476fc0e465a1e1579f53335ce901935efc5c67fdeb"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8000c6bf889e35e8b75407ea2d56153534b3f80c3b768378f4ca5a6fe286c0f"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7e20ead363d70b3f0f0b04cf6da30257d22a166700fa39e06c9f263b527688"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:444d2662912bc439c54c1b0ffe38354ae648f1f1ac8d1254b14fa768aa1a8587"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cceac9018359310fee46204b452860bfdcb3da00f4518d430790f909cbbf6b4c"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:332c182afbd9f7601e268426470e8c453740769a6227e7d1a9636d905cd7d707"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-win32.whl", hash = "sha256:25e993a41ad11fc433cb18ce0cc1d51eb7a285560c5cdddf781139312dac1881"}, + {file = "tree_sitter_languages-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:57419c215092ba9ba1964e07620dd386fc88ebb075b981fbb80f68f58004d4b4"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06747cac4789c436affa7c6b3483f68cc234e6a75b508a0f8369c77eb1faa04b"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b40bc82005543309c9cd4059f362c9d0d51277c942c71a5fdbed118389e5543a"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44920c9654ae03e94baa45c6e8c4b36a5f7bdd0c93877c72931bd77e862adaf1"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82e44f63a5449a41c5de3e9350967dc1c9183d9375881af5efb970c58c3fcfd8"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:df177fa87b655f6234e4dae540ba3917cf8e87c3646423b809415711e926765e"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:abdc8793328aa13fbd1cef3a0dff1c2e057a430fe2a64251628bbc97c4774eba"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b3f319f95f4464c35381755422f6dc0a518ad7d295d3cfe57bbaa564d225f3f"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-win32.whl", hash = "sha256:9f3a59bb4e8ec0a598566e02b7900eb8142236bda6c8b1069c4f3cdaf641950d"}, + {file = "tree_sitter_languages-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:517bdfe34bf24a05a496d441bee836fa77a6864f256508b82457ac28a9ac36bc"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9383331026f736bcbdf6b67f9b45417fe8fbb47225fe2517a1e4f974c319d9a8"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bba45ff3715e20e6e9a9b402f1ec2f2fc5ce11ce7b223584d0b5be5a4f8c60bb"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03558927c6e731d81706e3a8b26276eaa4fadba17e2fd83a5e0bc2a32b261975"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0231140e2d29fcf987216277483c93bc7ce4c2f88b8af77756d796e17a2957"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ead59b416f03da262df26e282cd40eb487f15384c90290f5105451e9a8ecfea"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fd27b7bdb95a2b35b730069d7dea60d0f6cc37e5ab2e900d2940a82d1db608bd"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d8b65a5fafd774a6c6dcacd9ac8b4c258c9f1efe2bfdca0a63818c83e591b949"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f32f7a7b8fd9952f82e2b881c1c8701a467b27db209590e0effb2fb4d71fe3d3"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-win32.whl", hash = "sha256:b52321e2a3a7cd1660cd7dadea16d7c7b9c981e177e0f77f9735e04cd89de015"}, + {file = "tree_sitter_languages-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8752bec9372937094a2557d9bfff357f30f5aa398e41e76e656baf53b4939d3"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:119f32cfc7c561e252e8958259ef997f2adfd4587ae43e82819b56f2810b8b42"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:582b04e11c67706b0a5ea64fd53ce4910fe11ad29d74ec7680c4014a02d09d4a"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a816f76c52f6c9fb3316c5d44195f8de48e09f2214b7fdb5f9232395033c789c"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a099b2f69cf22ab77de811b148de7d2d8ba8c51176a64bc56304cf42a627dd4"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:447b6c62c59255c89341ec0968e467e8c59c60fc5c2c3dc1f7dfe159a820dd3c"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41f4fee9b7de9646ef9711b6dbcdd5a4e7079e3d175089c8ef3f2c68b5adb5f4"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ee3b70594b79ff1155d5d9fea64e3af240d9327a52526d446e6bd792ac5b43cf"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:087b82cc3943fc5ffac30dc1b4192936a27c3c06fbd8718935a269e30dedc83b"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-win32.whl", hash = "sha256:155483058dc11de302f47922d31feec5e1bb9888e661aed7be0dad6f70bfe691"}, + {file = "tree_sitter_languages-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:5335405a937f788a2608d1b25c654461dddddbc6a1341672c833d2c8943397a8"}, ] [package.dependencies] @@ -2084,13 +2118,13 @@ files = [ [[package]] name = "types-tree-sitter" -version = "0.20.1.6" +version = "0.20.1.20240106" description = "Typing stubs for tree-sitter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "types-tree-sitter-0.20.1.6.tar.gz", hash = "sha256:310a97916adf73553fd1bda8107884da9b638550ddc76085ae0875c8f520520c"}, - {file = "types_tree_sitter-0.20.1.6-py3-none-any.whl", hash = "sha256:40eae13bc44f4e36d4e97b52db674fe808c6ccb3036a7aed9a736313411fd057"}, + {file = "types-tree-sitter-0.20.1.20240106.tar.gz", hash = "sha256:b0866a74942af5e223ceda9d1665befab9d55e0ccaa6704215efeaa2b02b0ca6"}, + {file = "types_tree_sitter-0.20.1.20240106-py3-none-any.whl", hash = "sha256:3b38b2500d3235f07644bc7148c5c9dbc929fdcb5d10d36709601028c9e0ebe2"}, ] [[package]] @@ -2120,13 +2154,13 @@ files = [ [[package]] name = "tzdata" -version = "2023.3" +version = "2023.4" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, ] [[package]] @@ -2342,4 +2376,4 @@ syntax = ["tree-sitter", "tree_sitter_languages"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "c4c26f6d0bd1266a7a38b9236c99cf51bf658447a18ffc2c96fb5da442762d6a" +content-hash = "1c4c8c5a2628c5d90869db7a106afcdcf6c71ab33f5c2e82877f20ddaf16e3f7" From f8635c17750e2b2900a109c4eac3e1ccfceb3d80 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:25:25 +0000 Subject: [PATCH 130/150] Rename wrap to soft_wrap --- src/textual/widgets/_text_area.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 3d17822485..3dbf92a0a6 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -232,7 +232,7 @@ class TextArea(ScrollView, can_focus=True): cursor_blink: Reactive[bool] = reactive(True) """True if the cursor should blink.""" - wrap: Reactive[bool] = reactive(False) + soft_wrap: Reactive[bool] = reactive(False) """True if text should soft wrap.""" _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False) @@ -276,7 +276,7 @@ def __init__( *, language: str | None = None, theme: str | None = None, - wrap: bool = False, + soft_wrap: bool = False, tab_behaviour: Literal["focus", "indent"] = "indent", name: str | None = None, id: str | None = None, @@ -350,7 +350,7 @@ def __init__( self.theme = theme - self._reactive_wrap = wrap + self._reactive_soft_wrap = soft_wrap self.tab_behaviour = tab_behaviour @@ -724,7 +724,7 @@ def _watch_wrap(self) -> None: def wrap_width(self) -> int: width, _ = self.scrollable_content_region.size cursor_width = 1 - if self.wrap: + if self.soft_wrap: return width - self.gutter_width - cursor_width return 0 @@ -780,7 +780,7 @@ def _yield_character_locations_reverse( def _refresh_size(self) -> None: """Update the virtual size of the TextArea.""" - if self.wrap: + if self.soft_wrap: self.virtual_size = Size(0, self.wrapped_document.height) else: # +1 width to make space for the cursor resting at the end of the line @@ -956,7 +956,7 @@ def render_line(self, widget_y: int) -> Strip: base_width = ( self.scrollable_content_region.size.width - if self.wrap + if self.soft_wrap else max(virtual_width, self.region.size.width) ) target_width = base_width - self.gutter_width @@ -971,7 +971,7 @@ def render_line(self, widget_y: int) -> Strip: text_strip = Strip(text_segments) # Crop the line to show only the visible part (some may be scrolled out of view) - if not self.wrap: + if not self.soft_wrap: text_strip = text_strip.crop( scroll_x, scroll_x + virtual_width - gutter_width ) From 32ea6ec2c0ea28f1e1c997dae112c4774c00987c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:29:09 +0000 Subject: [PATCH 131/150] Update Rich --- poetry.lock | 16 +++++++--------- pyproject.toml | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 196df5d5c9..2570c6097a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1750,21 +1750,19 @@ version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" -files = [] -develop = true +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] [package.dependencies] markdown-it-py = ">=2.2.0" -pygments = "^2.13.0" -typing-extensions = {version = ">=4.0.0, <5.0", markers = "python_version < \"3.9\""} +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] -[package.source] -type = "directory" -url = "../rich" - [[package]] name = "setuptools" version = "69.0.3" @@ -2376,4 +2374,4 @@ syntax = ["tree-sitter", "tree_sitter_languages"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "1c4c8c5a2628c5d90869db7a106afcdcf6c71ab33f5c2e82877f20ddaf16e3f7" +content-hash = "c4c26f6d0bd1266a7a38b9236c99cf51bf658447a18ffc2c96fb5da442762d6a" diff --git a/pyproject.toml b/pyproject.toml index 69b3ef8591..11f40dd68d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,8 @@ include = [ [tool.poetry.dependencies] python = "^3.8" markdown-it-py = { extras = ["plugins", "linkify"], version = ">=2.1.0" } -#rich = ">=13.3.3" -rich = {path="../rich", develop=true} +rich = ">=13.3.3" +#rich = {path="../rich", develop=true} typing-extensions = "^4.4.0" tree-sitter = { version = "^0.20.1", optional = true } tree_sitter_languages = { version = ">=1.7.0", optional = true } From 4b80cf3654828e420d1bfaf342bbb4e8d87e7a79 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 13:31:27 +0000 Subject: [PATCH 132/150] Fix Makefile whitespace --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0aa62facab..8b5f8d2191 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ run := poetry run .PHONY: test test: - $(run) pytest --cov-report term-missing --cov=textual tests/ -vv + $(run) pytest --cov-report term-missing --cov=textual tests/ -vv .PHONY: unit-test unit-test: From 9914cd0f9ae8f0445da216a14da58c32238cdc80 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 18 Jan 2024 17:01:42 +0000 Subject: [PATCH 133/150] WIP - precomputing tab sections --- src/textual/_wrap.py | 11 +++++++++-- src/textual/widgets/_text_area.py | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 31dbc0286a..9724365268 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -34,6 +34,7 @@ def compute_wrap_offsets( width: int, tab_size: int, fold: bool = True, + precomputed_tab_sections: list[tuple[str, int]] | None = None, # TODO consider this ) -> list[int]: """Given a string of text, and a width (measured in cells), return a list of codepoint indices which the string should be split at in order for it to fit @@ -44,17 +45,23 @@ def compute_wrap_offsets( width: The available cell width. tab_size: The tab stop width. fold: If True, words longer than `width` will be folded onto a new line. + precomputed_tab_sections: The output of `get_tab_widths` can be passed here directly, + to prevent us from having to recompute the value. Returns: A list of indices to break the line at. """ + tab_size = min(tab_size, width) + if precomputed_tab_sections: + tab_sections = precomputed_tab_sections + else: + tab_sections = get_tab_widths(text, tab_size) + break_positions: list[int] = [] # offsets to insert the breaks at append = break_positions.append cell_offset = 0 _cell_len = cell_len - tab_size = min(tab_size, width) - tab_sections = get_tab_widths(text, tab_size) tab_section_index = 0 cumulative_width = 0 cumulative_widths = [] # Accumulated tab widths of all codepoints prior (exclusive) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 3dbf92a0a6..6e3624c9ff 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -962,7 +962,13 @@ def render_line(self, widget_y: int) -> Strip: target_width = base_width - self.gutter_width console = self.app.console gutter_segments = console.render(gutter) + + # TODO - we can't just expand tabs here, because the width of the tab + # was earlier computed while the tab was a document line, + # we need to expand them back to the width we calculated inside the document line, + # pre-division. line.expand_tabs(self.indent_width) + text_segments = list( console.render(line, console.options.update_width(target_width)) ) From 12a04239b0f3aad45f372af35114ef5e7ddba9a6 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jan 2024 15:28:25 +0000 Subject: [PATCH 134/150] Applying correct tab widths after wrap/fold --- src/textual/_wrap.py | 2 +- src/textual/document/_wrapped_document.py | 69 ++++++++++++++++++++--- src/textual/expand_tabs.py | 23 ++++++++ src/textual/widgets/_text_area.py | 31 +++++++--- 4 files changed, 109 insertions(+), 16 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index 9724365268..e47f3fb570 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -34,7 +34,7 @@ def compute_wrap_offsets( width: int, tab_size: int, fold: bool = True, - precomputed_tab_sections: list[tuple[str, int]] | None = None, # TODO consider this + precomputed_tab_sections: list[tuple[str, int]] | None = None, ) -> list[int]: """Given a string of text, and a width (measured in cells), return a list of codepoint indices which the string should be split at in order for it to fit diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 0e737bc822..273207e60c 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -14,7 +14,7 @@ from textual._cells import cell_len, cell_width_to_column_index from textual._wrap import compute_wrap_offsets from textual.document._document import DocumentBase, Location -from textual.expand_tabs import expand_tabs_inline +from textual.expand_tabs import expand_tabs_inline, get_tab_widths from textual.geometry import Offset, clamp VerticalOffset = int @@ -46,6 +46,10 @@ def __init__( """Maps line indices to the offsets within the line where wrapping breaks should be added.""" + self._tab_width_cache: list[list[int]] = [] + """Maps line indices to a list of tab widths. `[[2, 4]]` means that on line 0, the first + tab has width 2, and the second tab has width 4.""" + self._offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] """Maps y_offsets (from the top of the document) to line_index and the offset of the section within the line.""" @@ -61,6 +65,14 @@ def __init__( self.wrap(width, tab_width) + @property + def wrapped(self) -> bool: + """True if the content is wrapped. This is not the same as wrapping being enabled. + For example, an empty document can have wrapping enabled, but no wrapping has actually + occurred. In other words, this is True if the length of any line in the document is greater + than the available width.""" + return len(self._line_index_to_offsets) == len(self._offset_to_line_info) + def wrap(self, width: int, tab_width: int | None = None) -> None: """Wrap and cache all lines in the document. @@ -77,25 +89,39 @@ def wrap(self, width: int, tab_width: int | None = None) -> None: new_wrap_offsets: list[list[int]] = [] offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] line_index_to_offsets: list[list[VerticalOffset]] = [] + line_tab_widths: list[list[int]] = [] append_wrap_offset = new_wrap_offsets.append - current_offset = 0 + append_line_info = offset_to_line_info.append + append_line_offsets = line_index_to_offsets.append + append_line_tab_widths = line_tab_widths.append + current_offset = 0 tab_width = self._tab_width for line_index, line in enumerate(self.document.lines): + tab_sections = get_tab_widths(line, tab_width) wrap_offsets = ( - compute_wrap_offsets(line, width, tab_size=tab_width) if width else [] + compute_wrap_offsets( + line, + width, + tab_size=tab_width, + precomputed_tab_sections=tab_sections, + ) + if width + else [] ) + append_line_tab_widths([width for _, width in tab_sections]) append_wrap_offset(wrap_offsets) - line_index_to_offsets.append([]) + append_line_offsets([]) for section_y_offset in range(len(wrap_offsets) + 1): - offset_to_line_info.append((line_index, section_y_offset)) + append_line_info((line_index, section_y_offset)) line_index_to_offsets[line_index].append(current_offset) current_offset += 1 + self._wrap_offsets = new_wrap_offsets self._offset_to_line_info = offset_to_line_info self._line_index_to_offsets = line_index_to_offsets - self._wrap_offsets = new_wrap_offsets + self._tab_width_cache = line_tab_widths @property def lines(self) -> list[list[str]]: @@ -170,14 +196,26 @@ def wrap_range( new_wrap_offsets: list[list[int]] = [] new_line_index_to_offsets: list[list[VerticalOffset]] = [] new_offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] + new_tab_widths: list[int] = [] + append_wrap_offsets = new_wrap_offsets.append + append_tab_widths = new_tab_widths.append + width = self._width + tab_width = self._tab_width # Add the new offsets between the top and new bottom (the new post-edit offsets) current_y_offset = top_y_offset - tab_width = self._tab_width for line_index, line in enumerate(new_lines, top_line_index): - wrap_offsets = compute_wrap_offsets(line, width, tab_width) if width else [] + tab_sections = get_tab_widths(line, tab_width) + wrap_offsets = ( + compute_wrap_offsets( + line, width, tab_width, precomputed_tab_sections=tab_sections + ) + if width + else [] + ) + append_tab_widths([width for _, width in tab_sections]) append_wrap_offsets(wrap_offsets) # Collect up the new y offsets for this document line @@ -199,6 +237,10 @@ def wrap_range( top_line_index : old_bottom_line_index + 1 ] = new_line_index_to_offsets + self._tab_width_cache[ + top_line_index : old_bottom_line_index + 1 + ] = new_tab_widths + # How much did the edit/rewrap alter the offsets? old_height = old_bottom_y_offset - top_y_offset + 1 new_height = len(new_offset_to_line_info) @@ -382,3 +424,14 @@ def get_offsets(self, line_index: int) -> list[int]: f"The document contains {len(wrap_offsets)!r} lines." ) return wrap_offsets[line_index] + + def get_tab_widths(self, line_index: int) -> list[int]: + """Return a list of the tab widths for the given line index. + + Args: + line_index: The index of the line in the document. + + Returns: + An ordered list of the expanded width of the tabs in the line. + """ + return self._tab_width_cache[line_index] diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 8b51aca615..0897de3367 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -3,6 +3,7 @@ import re from rich.cells import cell_len +from rich.text import Text _TABS_SPLITTER_RE = re.compile(r"(.*?\t|.+?$)") @@ -60,6 +61,28 @@ def expand_tabs_inline(line: str, tab_size: int = 4) -> str: ) +def expand_text_tabs_inline(line: Text, tab_widths: list[int]) -> Text: + """Expand tabs to the widths defined in the `tab_widths` list, retaining style information + across the expanded tab characters. + """ + if "\t" not in line.plain: + return line + + parts = line.split("\t", include_separator=True) + tab_widths_iter = iter(tab_widths) + + new_parts = [] + append_part = new_parts.append + for part in parts: + if part.plain.endswith("\t"): + part._text[-1] = part._text[-1][:-1] + " " + spaces = next(tab_widths_iter) + part.extend_style(spaces - 1) + append_part(part) + + return Text("", end="").join(new_parts) + + if __name__ == "__main__": print(expand_tabs_inline("\tbar")) print(expand_tabs_inline("\tbar\t")) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 6e3624c9ff..25bbef0c82 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -29,7 +29,7 @@ SyntaxAwareDocumentError, ) from textual.document._wrapped_document import WrappedDocument -from textual.expand_tabs import expand_tabs_inline +from textual.expand_tabs import expand_tabs_inline, expand_text_tabs_inline if TYPE_CHECKING: from tree_sitter import Language @@ -518,6 +518,10 @@ def _watch_indent_width(self) -> None: self._rewrap_and_refresh_virtual_size() self.scroll_cursor_visible() + def _watch_show_vertical_scrollbar(self) -> None: + self._rewrap_and_refresh_virtual_size() + self.scroll_cursor_visible() + def _watch_theme(self, theme: str | None) -> None: """We set the styles on this widget when the theme changes, to ensure that if padding is applied, the colours match.""" @@ -952,8 +956,27 @@ def render_line(self, widget_y: int) -> Strip: if wrap_offsets: sections = line.divide(wrap_offsets) # TODO cache result with edit count line = sections[section_offset] + line_tab_widths = wrapped_document.get_tab_widths(line_index) line.end = "" + # Get the widths of the tabs corresponding only to the section of the + # line that is currently being rendered. We don't care about tabs in + # other sections of the same line. + + # Count the tabs before this section. + tabs_before = 0 + for section_index in range(section_offset): + tabs_before += sections[section_index].plain.count("\t") + + # Count the tabs in this section. + tabs_within = line.plain.count("\t") + section_tab_widths = line_tab_widths[ + tabs_before : tabs_before + tabs_within + ] + line = expand_text_tabs_inline(line, section_tab_widths) + else: + line.expand_tabs(self.indent_width) + base_width = ( self.scrollable_content_region.size.width if self.soft_wrap @@ -963,12 +986,6 @@ def render_line(self, widget_y: int) -> Strip: console = self.app.console gutter_segments = console.render(gutter) - # TODO - we can't just expand tabs here, because the width of the tab - # was earlier computed while the tab was a document line, - # we need to expand them back to the width we calculated inside the document line, - # pre-division. - line.expand_tabs(self.indent_width) - text_segments = list( console.render(line, console.options.update_width(target_width)) ) From e77d8a01df5d8cd42cd4ec0864fb1d8f292cc5e8 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Mon, 22 Jan 2024 16:18:02 +0000 Subject: [PATCH 135/150] Fix watcher name --- src/textual/widgets/_text_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 25bbef0c82..e76e56117f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -720,7 +720,7 @@ def load_text(self, text: str) -> None: def _on_resize(self) -> None: self._rewrap_and_refresh_virtual_size() - def _watch_wrap(self) -> None: + def _watch_soft_wrap(self) -> None: self._rewrap_and_refresh_virtual_size() self.call_after_refresh(self.scroll_cursor_visible, center=True) From 069bedbf758a64739140aa94651b0808c2c108b9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 10:29:34 +0000 Subject: [PATCH 136/150] Create snapshot test for wrapping and folding --- .../__snapshots__/test_snapshots.ambr | 169 ++++++++++++++++++ .../snapshot_apps/text_area_wrapping.py | 31 ++++ tests/snapshot_tests/test_snapshots.py | 7 + 3 files changed, 207 insertions(+) create mode 100644 tests/snapshot_tests/snapshot_apps/text_area_wrapping.py diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 2e0826421e..af8bffb828 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -36436,6 +36436,175 @@ ''' # --- +# name: test_text_area_wrapping_and_folding + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaWrapping + + + + + + + + + +  1  # The  + Wonders of  + Space  + Exploration +  2   +  3  Space  + exploration  + has *always* + captured the  + human  + imagination.  +  4   +  5  ダレンバーン + ズ  +  6   +  7   + Thisissomelon + gtextthatshou + ldfoldcorrect + ly.  +  8   +  9  ▇▇ + ダレン  + バーンズ  + 10   + 11   + + + + + ''' +# --- # name: test_text_log_blank_write ''' diff --git a/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py b/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py new file mode 100644 index 0000000000..8a3a64079e --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py @@ -0,0 +1,31 @@ +from textual.app import App, ComposeResult +from textual.widgets import TextArea + +TEXT = """\ +# The Wonders of Space Exploration + +Space exploration has *always* captured the human imagination. + +ダレンバーンズ + +\tThisissomelongtextthatshouldfoldcorrectly. + +\t\tダレン バーンズ + + + + + + + +""" + + +class TextAreaWrapping(App): + def compose(self) -> ComposeResult: + yield TextArea(TEXT, language="markdown", soft_wrap=True) + + +app = TextAreaWrapping() +if __name__ == "__main__": + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 63e5d93026..f13c72eff9 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -876,6 +876,13 @@ def setup_theme(pilot): ) +def test_text_area_wrapping_and_folding(snap_compare): + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area_wrapping.py", + terminal_size=(20, 26) + ) + + def test_digits(snap_compare) -> None: assert snap_compare(SNAPSHOT_APPS_DIR / "digits.py") From f496c11a2f6318e83df56f6e8a012a70cd778f2f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 10:39:26 +0000 Subject: [PATCH 137/150] Ensure we only wrap once on load --- src/textual/widgets/_text_area.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index e76e56117f..9bb6bd21c4 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -214,28 +214,28 @@ class TextArea(ScrollView, can_focus=True): The text selected in the document is available via the `TextArea.selected_text` property. """ - show_line_numbers: Reactive[bool] = reactive(True) + show_line_numbers: Reactive[bool] = reactive(True, init=False) """True to show the line number column on the left edge, otherwise False. Changing this value will immediately re-render the `TextArea`.""" - indent_width: Reactive[int] = reactive(4) + indent_width: Reactive[int] = reactive(4, init=False) """The width of tabs or the multiple of spaces to align to on pressing the `tab` key. If the document currently open contains tabs that are currently visible on screen, altering this value will immediately change the display width of the visible tabs. """ - match_cursor_bracket: Reactive[bool] = reactive(True) + match_cursor_bracket: Reactive[bool] = reactive(True, init=False) """If the cursor is at a bracket, highlight the matching bracket (if found).""" - cursor_blink: Reactive[bool] = reactive(True) + cursor_blink: Reactive[bool] = reactive(True, init=False) """True if the cursor should blink.""" - soft_wrap: Reactive[bool] = reactive(False) + soft_wrap: Reactive[bool] = reactive(False, init=False) """True if text should soft wrap.""" - _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False) + _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False, init=False) """Indicates where the cursor is in the blink cycle. If it's currently not visible due to blinking, this is False.""" From ba6b0c73a33f1f514443dc4ae19da692f90014df Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 10:43:02 +0000 Subject: [PATCH 138/150] Add syntax mark to test --- tests/snapshot_tests/test_snapshots.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index f13c72eff9..ec51437e84 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -876,6 +876,7 @@ def setup_theme(pilot): ) +@pytest.mark.syntax def test_text_area_wrapping_and_folding(snap_compare): assert snap_compare( SNAPSHOT_APPS_DIR / "text_area_wrapping.py", From b122e941f1fb46a90fbef85c420e4dadd318fdbf Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 11:07:57 +0000 Subject: [PATCH 139/150] Create code_editor constructor --- src/textual/widgets/_text_area.py | 56 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 9bb6bd21c4..833ea3e20f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -232,7 +232,7 @@ class TextArea(ScrollView, can_focus=True): cursor_blink: Reactive[bool] = reactive(True, init=False) """True if the cursor should blink.""" - soft_wrap: Reactive[bool] = reactive(False, init=False) + soft_wrap: Reactive[bool] = reactive(True, init=False) """True if text should soft wrap.""" _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False, init=False) @@ -276,8 +276,9 @@ def __init__( *, language: str | None = None, theme: str | None = None, - soft_wrap: bool = False, - tab_behaviour: Literal["focus", "indent"] = "indent", + soft_wrap: bool = True, + tab_behaviour: Literal["focus", "indent"] = "focus", + show_line_numbers: bool = False, name: str | None = None, id: str | None = None, classes: str | None = None, @@ -289,7 +290,9 @@ def __init__( text: The initial text to load into the TextArea. language: The language to use. theme: The theme to use. + soft_wrap: Enable soft wrapping. tab_behaviour: If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab. + show_line_numbers: Show line numbers on the left edge. name: The name of the `TextArea` widget. id: The ID of the widget, used to refer to it from Textual CSS. classes: One or more Textual CSS compatible class names separated by spaces. @@ -352,8 +355,55 @@ def __init__( self._reactive_soft_wrap = soft_wrap + self._reactive_show_line_numbers = show_line_numbers + self.tab_behaviour = tab_behaviour + @classmethod + def code_editor( + cls, + text: str = "", + *, + language: str | None = None, + theme: str | None = None, + soft_wrap: bool = False, + tab_behaviour: Literal["focus", "indent"] = "indent", + show_line_numbers: bool = True, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ) -> TextArea: + """Construct a new `TextArea` with sensible defaults for editing code. + + This instantiates a `TextArea` with line numbers enabled, soft wrapping + disabled, and "indent" tab behaviour. + + Args: + text: The initial text to load into the TextArea. + language: The language to use. + theme: The theme to use. + soft_wrap: Enable soft wrapping. + tab_behaviour: If 'focus', pressing tab will switch focus. If 'indent', pressing tab will insert a tab. + show_line_numbers: Show line numbers on the left edge. + name: The name of the `TextArea` widget. + id: The ID of the widget, used to refer to it from Textual CSS. + classes: One or more Textual CSS compatible class names separated by spaces. + disabled: True if the widget is disabled. + """ + return TextArea( + text, + language=language, + theme=theme, + soft_wrap=soft_wrap, + tab_behaviour=tab_behaviour, + show_line_numbers=show_line_numbers, + name=name, + id=id, + classes=classes, + disabled=disabled, + ) + @staticmethod def _get_builtin_highlight_query(language_name: str) -> str: """Get the highlight query for a builtin language. From 059e6b8e442d22cde4a921d0d447f3e76e1ced89 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 13:50:25 +0000 Subject: [PATCH 140/150] Updating tests to account for new defaults --- src/textual/widgets/_text_area.py | 6 +++--- tests/snapshot_tests/snapshot_apps/text_area.py | 2 +- tests/snapshot_tests/snapshot_apps/text_area_unfocus.py | 2 +- tests/snapshot_tests/snapshot_apps/text_area_wrapping.py | 2 +- tests/text_area/test_edit_via_bindings.py | 2 +- tests/text_area/test_selection.py | 4 ++-- tests/text_area/test_selection_bindings.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 833ea3e20f..a49673f54b 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -341,6 +341,7 @@ def __init__( action, accounting for wrapping etc.""" self._cursor_offset = (0, 0) + """The virtual offset of the cursor (not screen-space offset).""" self._set_document(text, language) @@ -474,9 +475,8 @@ def _watch_selection( match_location = self.find_matching_bracket(character, cursor_location) self._matching_bracket_location = match_location if match_location is not None: - match_row, match_column = match_location - if match_row in range(*self._visible_line_indices): - self.refresh_lines(match_row) + _, offset_y = self._cursor_offset + self.refresh_lines(offset_y) self.app.cursor_position = self.cursor_screen_offset if previous_selection != selection: diff --git a/tests/snapshot_tests/snapshot_apps/text_area.py b/tests/snapshot_tests/snapshot_apps/text_area.py index da6bd06992..9822d2fafc 100644 --- a/tests/snapshot_tests/snapshot_apps/text_area.py +++ b/tests/snapshot_tests/snapshot_apps/text_area.py @@ -5,7 +5,7 @@ class TextAreaSnapshot(App): def compose(self) -> ComposeResult: - text_area = TextArea() + text_area = TextArea.code_editor() text_area.cursor_blink = False yield text_area diff --git a/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py b/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py index e092f16721..cc373b593f 100644 --- a/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py +++ b/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py @@ -7,7 +7,7 @@ class TextAreaUnfocusSnapshot(App): AUTO_FOCUS = None def compose(self) -> ComposeResult: - text_area = TextArea() + text_area = TextArea.code_editor() text_area.cursor_blink = False yield text_area diff --git a/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py b/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py index 8a3a64079e..8264438be4 100644 --- a/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py +++ b/tests/snapshot_tests/snapshot_apps/text_area_wrapping.py @@ -23,7 +23,7 @@ class TextAreaWrapping(App): def compose(self) -> ComposeResult: - yield TextArea(TEXT, language="markdown", soft_wrap=True) + yield TextArea.code_editor(TEXT, language="markdown", soft_wrap=True) app = TextAreaWrapping() diff --git a/tests/text_area/test_edit_via_bindings.py b/tests/text_area/test_edit_via_bindings.py index b4a3e6444f..f33e24b5f4 100644 --- a/tests/text_area/test_edit_via_bindings.py +++ b/tests/text_area/test_edit_via_bindings.py @@ -29,7 +29,7 @@ class TextAreaApp(App): def compose(self) -> ComposeResult: - text_area = TextArea() + text_area = TextArea.code_editor() text_area.load_text(TEXT) yield text_area diff --git a/tests/text_area/test_selection.py b/tests/text_area/test_selection.py index bbc70e476e..78cce7ad0f 100644 --- a/tests/text_area/test_selection.py +++ b/tests/text_area/test_selection.py @@ -301,7 +301,7 @@ async def test_select_line(index, content, expected_selection): async def test_cursor_screen_offset_and_terminal_cursor_position_update(): class TextAreaCursorScreenOffset(App): def compose(self) -> ComposeResult: - yield TextArea("abc\ndef") + yield TextArea.code_editor("abc\ndef") app = TextAreaCursorScreenOffset() async with app.run_test(): @@ -321,7 +321,7 @@ def compose(self) -> ComposeResult: async def test_cursor_screen_offset_and_terminal_cursor_position_scrolling(): class TextAreaCursorScreenOffset(App): def compose(self) -> ComposeResult: - yield TextArea("AB\nAB\nAB\nAB\nAB\nAB\n") + yield TextArea.code_editor("AB\nAB\nAB\nAB\nAB\nAB\n") app = TextAreaCursorScreenOffset() async with app.run_test(size=(80, 2)) as pilot: diff --git a/tests/text_area/test_selection_bindings.py b/tests/text_area/test_selection_bindings.py index 59899a318b..d5aa024fcc 100644 --- a/tests/text_area/test_selection_bindings.py +++ b/tests/text_area/test_selection_bindings.py @@ -14,7 +14,7 @@ class TextAreaApp(App): def compose(self) -> ComposeResult: - text_area = TextArea() + text_area = TextArea(show_line_numbers=True) text_area.load_text(TEXT) yield text_area From fdd64c9243ecf04e19b9832805c5fc6235c7f44f Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 14:15:54 +0000 Subject: [PATCH 141/150] Renaming --- src/textual/document/_document_navigator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index ccac2f23be..da48ab65b6 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -295,9 +295,9 @@ def get_location_home( line = self._wrapped_document.document[line_index] target_column = 0 if smart_home: - for index, code_point in enumerate(line): + for code_point_index, code_point in enumerate(line): if not code_point.isspace(): - target_column = index + target_column = code_point_index break if column_offset == 0 or column_offset > target_column: From a6788472886cd194156e9ba7f3c4d6c02248ca50 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 14:39:50 +0000 Subject: [PATCH 142/150] Updating CHANGELOG, docs --- CHANGELOG.md | 12 +++++++++++- src/textual/widgets/_text_area.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a95d744f..db06632214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Breaking change: Significant changes to `TextArea.__init__` default values/behaviour https://github.com/Textualize/textual/pull/3933 + - `soft_wrap=True` - soft wrapping is now enabled by default. + - `show_line_numbers=False` - line numbers are now disabled by default. + - `tab_behaviour="focus"` - pressing the tab key now switches focus instead of indenting by default. - Breaking change: `DOMNode.has_pseudo_class` now accepts a single name only https://github.com/Textualize/textual/pull/3970 - Made `textual.cache` (formerly `textual._cache`) public https://github.com/Textualize/textual/pull/3976 - `Tab.label` can now be used to change the label of a tab https://github.com/Textualize/textual/pull/3979 @@ -18,6 +22,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `DOMNode.has_pseudo_classes` https://github.com/Textualize/textual/pull/3970 - Added `Widget.allow_focus` and `Widget.allow_focus_children` https://github.com/Textualize/textual/pull/3989 +- Added `TextArea.soft_wrap` reactive attribute added https://github.com/Textualize/textual/pull/3933 +- Added `TextArea.tab_behaviour` reactive attribute added https://github.com/Textualize/textual/pull/3933 +- Added `TextArea.code_editor` classmethod/alternative constructor https://github.com/Textualize/textual/pull/3933 +- Added `TextArea.wrapped_document` attribute which can convert between wrapped visual coordinates and locations https://github.com/Textualize/textual/pull/3933 +- Added `show_line_numbers` to `TextArea.__init__` https://github.com/Textualize/textual/pull/3933 ### Fixed @@ -27,7 +36,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `SelectionList` option IDs are usable as soon as the widget is instantiated https://github.com/Textualize/textual/issues/3903 - Fix issue with `Strip.crop` when crop window start aligned with strip end https://github.com/Textualize/textual/pull/3998 - Fixed Strip.crop_extend https://github.com/Textualize/textual/pull/4011 -- Fixed a crash if the `TextArea` language was set but tree-sitter lanuage binaries were not installed https://github.com/Textualize/textual/issues/4045 +- Fixed a crash if the `TextArea` language was set but tree-sitter language binaries were not installed https://github.com/Textualize/textual/issues/4045 +- Ensuring `TextArea.SelectionChanged` message only sends when the updated selection is different https://github.com/Textualize/textual/pull/3933 ## [0.47.1] - 2023-01-05 diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index a49673f54b..443295c975 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1357,7 +1357,7 @@ def move_cursor_relative( center: bool = False, record_width: bool = True, ) -> None: - """Move the cursor relative to its current location. + """Move the cursor relative to its current location in document-space. Args: rows: The number of rows to move down by (negative to move up) From 65aee34a3e3f5b6edd981b045c696cbee8800457 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 15:18:39 +0000 Subject: [PATCH 143/150] Docstrings and small renamings --- src/textual/document/_document_navigator.py | 215 +++++++++++++++----- src/textual/document/_wrapped_document.py | 37 ++-- src/textual/widgets/_text_area.py | 4 +- 3 files changed, 193 insertions(+), 63 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index da48ab65b6..db97b4479e 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -1,35 +1,3 @@ -"""Cursor navigation in the TextArea is "wrapping-aware". - -Although the cursor location (the selection) is represented as a location -in the raw document, when you actually *move* the cursor, it must take wrapping -into account (otherwise things start to look really confusing to the user where -wrapping is involved). - -Your cursor visually moves through the wrapped version of the document, rather -than the raw document. So, for example, pressing down on the keyboard -may move your cursor to a position further along the current line, -rather than on to the next line in the raw document. - -The class manages this behaviour. - -Given a cursor location in the unwrapped document, and a cursor movement action, -this class can inform us of the destination the cursor will move to considering -the current wrapping width and document content. - -For this to work correctly, the wrapped_document and document must be synchronised. -This means that if you make an edit to the document, you *must* then update the -wrapped document, and *then* you may query the document navigator. - -Naming conventions: - -A line "wrapped section" refers to a portion of the line accounting for wrapping. -For example the line "ABCDEF" when wrapped at width 3 will result in 2 sections: -"ABC" and "DEF". - -A "wrap offset" is an integer representing the index at which wrapping occurs in a line. -In "ABCDEF" with wrapping at width 3, there is a single wrap offset of 3. -""" - import re from bisect import bisect, bisect_left, bisect_right from typing import Any, Sequence @@ -41,6 +9,52 @@ class DocumentNavigator: + """Cursor navigation in the TextArea is "wrapping-aware". + + Although the cursor location (the selection) is represented as a location + in the raw document, when you actually *move* the cursor, it must take wrapping + into account (otherwise things start to look really confusing to the user where + wrapping is involved). + + Your cursor visually moves through the wrapped version of the document, rather + than the raw document. So, for example, pressing down on the keyboard + may move your cursor to a position further along the current document line, + rather than on to the next line in the raw document. + + The DocumentNavigator class manages that behaviour. + + Given a cursor location in the unwrapped document, and a cursor movement action, + this class can inform us of the destination the cursor will move to considering + the current wrapping width and document content. It can also translate between + document-space (a location/(row,col) in the raw document), and visual-space + (x and y offsets) as the user will see them on screen after the document has been + wrapped. + + For this to work correctly, the wrapped_document and document must be synchronised. + This means that if you make an edit to the document, you *must* then update the + wrapped document, and *then* you may query the document navigator. + + Naming conventions: + + A "location" refers to a location, in document-space (in the raw document). It + is entirely unrelated to visually positioning. A location in a document can appear + in any visual position, as it is influenced by scrolling, wrapping, gutter settings, + and the cell width of characters to its left. + + A "wrapped section" refers to a portion of the line accounting for wrapping. + For example the line "ABCDEF" when wrapped at width 3 will result in 2 sections: + "ABC" and "DEF". In this case, we call "ABC" is the first section/wrapped section. + + A "wrap offset" is an integer representing the index at which wrapping occurs in a + document-space line. This is a codepoint index, rather than a visual offset. + In "ABCDEF" with wrapping at width 3, there is a single wrap offset of 3. + + "Smart home" refers to a modification of the "home" key behaviour. If smart home is + enabled, the first non-whitespace character is considered to be the home location. + If the cursor is currently at this position, then the normal home behaviour applies. + This is designed to make cursor movement more useful to end users. + """ + def __init__(self, wrapped_document: WrappedDocument) -> None: """Create a DocumentNavigator. @@ -85,13 +99,27 @@ def is_start_of_wrapped_line(self, location: Location) -> bool: return index(wrap_offsets, column) != -1 def is_end_of_document_line(self, location: Location) -> bool: - """True if the location is at the end of a line in the document.""" + """True if the location is at the end of a line in the document. + + Args: + location: The location to examine. + + Returns: + True if and only if the document is on the last line of the document. + """ row, column = location row_length = len(self._document[row]) return column == row_length def is_end_of_wrapped_line(self, location: Location) -> bool: - """True if the location is at the end of a wrapped line.""" + """True if the location is at the end of a wrapped line. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is on the last wrapped section of *any* line. + """ if self.is_end_of_document_line(location): return True @@ -100,11 +128,25 @@ def is_end_of_wrapped_line(self, location: Location) -> bool: return index(wrap_offsets, column - 1) != -1 def is_first_document_line(self, location: Location) -> bool: + """Check if the given location is on the first line in the document. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is on the first line of the document. + """ return location[0] == 0 def is_first_wrapped_line(self, location: Location) -> bool: - # Ensure that the column index of the location is less (or equal to?!?!) than - # the first value in the wrap offsets. + """Check if the given location is on the first wrapped section of the first line in the document. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is on the first wrapped section of the first line. + """ if not self.is_first_document_line(location): return False @@ -119,10 +161,27 @@ def is_first_wrapped_line(self, location: Location) -> bool: return False def is_last_document_line(self, location: Location) -> bool: - """True when the location is on the last line of the document.""" + """Check if the given location is on the last line of the document. + + Args: + location: The location to examine. + + Returns: + True when the location is on the last line of the document. + """ return location[0] == self._document.line_count - 1 def is_last_wrapped_line(self, location: Location) -> bool: + """Check if the given location is on the last wrapped section of the last line. + + That is, the cursor is *visually* on the last rendered row. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is on the last section of the last line. + """ if not self.is_last_document_line(location): return False @@ -137,15 +196,41 @@ def is_last_wrapped_line(self, location: Location) -> bool: return False def is_start_of_document(self, location: Location) -> bool: - """True if and only if the cursor is at location (0, 0)""" + """Check if a location is at the start of the document. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is at document location (0, 0)""" return location == (0, 0) def is_end_of_document(self, location: Location) -> bool: + """Check if a location is at the end of the document. + + Args: + location: The location to examine. + + Returns: + True if and only if the cursor is at the end of the document. + """ return self.is_last_document_line(location) and self.is_end_of_document_line( location ) def get_location_left(self, location: Location) -> Location: + """Get the location to the left of the given location. + + Note that if the given location is at the start of the line, then + this will return the end of the preceding line, since that's where + you would expect the cursor to move. + + Args: + location: The location to start from. + + Returns: + The location to the right. + """ if location == (0, 0): return 0, 0 @@ -156,6 +241,18 @@ def get_location_left(self, location: Location) -> Location: return target_row, target_column def get_location_right(self, location: Location) -> Location: + """Get the location to the right of the given location. + + Note that if the given location is at the end of the line, then + this will return the start of the following line, since that's where + you would expect the cursor to move. + + Args: + location: The location to start from. + + Returns: + The location to the right. + """ if self.is_end_of_document(location): return location row, column = location @@ -164,9 +261,15 @@ def get_location_right(self, location: Location) -> Location: target_column = 0 if is_end_of_line else column + 1 return target_row, target_column - def get_location_above( - self, location: Location, maintain_offset: bool = False - ) -> Location: + def get_location_above(self, location: Location) -> Location: + """Get the location visually aligned with the cell above the given location. + + Args: + location: The location to start from. + + Returns: + The cell above the given location. + """ """Get the location up from the given location in the wrapped document.""" # Get the wrap offsets of the current line. @@ -207,16 +310,13 @@ def get_location_above( return target_location - def get_location_below( - self, location: Location, maintain_offset: bool = True - ) -> Location: + def get_location_below(self, location: Location) -> Location: """Given a location in the raw document, return the raw document location corresponding to moving down in the wrapped representation of the document. Args: location: The location in the raw document. - maintain_offset: Maintain the visual x-offset of the cursor. Returns: The location which is *visually* below the given location. @@ -282,7 +382,15 @@ def get_location_end(self, location: Location) -> Location: def get_location_home( self, location: Location, smart_home: bool = False ) -> Location: - """Get the location corresponding to the start of the current section.""" + """Get the "home location" corresponding to the given location. + + Args: + location: The location to consider. + smart_home: Enable/disable 'smart home' behaviour. + + Returns: + The home location, relative to the given location. + """ line_index, column_offset = location wrap_offsets = self._wrapped_document.get_offsets(line_index) if wrap_offsets: @@ -305,14 +413,23 @@ def get_location_home( return line_index, 0 - def get_location_offset_relative( - self, location: Location, offset_delta: int + def get_location_at_y_offset( + self, location: Location, vertical_offset: int ) -> Location: + """Apply a visual vertical offset to a location and check the resulting location. + + Args: + location: The location to start from. + vertical_offset: The vertical offset to move (negative=up, positive=down). + + Returns: + The location after the offset has been applied. + """ # Convert into offset-space to apply the offset. x_offset, y_offset = self._wrapped_document.location_to_offset(location) # Convert the offset with the delta applied back to location-space. return self._wrapped_document.offset_to_location( - Offset(x_offset, y_offset + offset_delta), + Offset(x_offset, y_offset + vertical_offset), ) def clamp_reachable(self, location: Location) -> Location: diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 273207e60c..51feb28482 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -1,10 +1,3 @@ -"""A view into a Document which wraps the document at a certain -width and can be queried to retrieve lines from the *wrapped* version -of the document. - -Allows for incremental updates, ensuring that we only re-wrap ranges of the document -that were influenced by edits. -""" from __future__ import annotations from bisect import bisect_right @@ -23,6 +16,14 @@ class WrappedDocument: + """A view into a Document which wraps the document at a certain + width and can be queried to retrieve lines from the *wrapped* version + of the document. + + Allows for incremental updates, ensuring that we only re-wrap ranges of the document + that were influenced by edits. + """ + def __init__( self, document: DocumentBase, @@ -62,14 +63,17 @@ def __init__( the value last passed into the `wrap` method.""" self._tab_width: int = tab_width + """The maximum width to expand tabs to when considering their widths.""" self.wrap(width, tab_width) @property def wrapped(self) -> bool: - """True if the content is wrapped. This is not the same as wrapping being enabled. + """True if the content is wrapped. This is not the same as wrapping being "enabled". For example, an empty document can have wrapping enabled, but no wrapping has actually - occurred. In other words, this is True if the length of any line in the document is greater + occurred. + + In other words, this is True if the length of any line in the document is greater than the available width.""" return len(self._line_index_to_offsets) == len(self._offset_to_line_info) @@ -162,9 +166,6 @@ def wrap_range( old_end: The old end location of the edit in document-space. new_end: The new end location of the edit in document-space. """ - - # Get all the text on the lines between start and end in document space - start_line_index, _ = start old_end_line_index, _ = old_end new_end_line_index, _ = new_end @@ -399,6 +400,18 @@ def get_target_document_column( return target_column_index def get_sections(self, line_index: int) -> list[str]: + """Return the sections for the given line index. + + When wrapping is enabled, a single line in the document can visually span + multiple lines. The list returned represents that visually (each string in + the list represents a single section (y-offset) after wrapping happens). + + Args: + line_index: The index of the line to get sections for. + + Returns: + The wrapped line as a list of strings. + """ line_offsets = self._wrap_offsets[line_index] wrapped_lines = Text(self.document[line_index], end="").divide(line_offsets) return [line.plain for line in wrapped_lines] diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 443295c975..346eaef9b5 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -1642,7 +1642,7 @@ def action_cursor_page_up(self) -> None: """Move the cursor and scroll up one page.""" height = self.content_size.height _, cursor_location = self.selection - target = self._navigator.get_location_offset_relative( + target = self._navigator.get_location_at_y_offset( cursor_location, -height, ) @@ -1653,7 +1653,7 @@ def action_cursor_page_down(self) -> None: """Move the cursor and scroll down one page.""" height = self.content_size.height _, cursor_location = self.selection - target = self._navigator.get_location_offset_relative( + target = self._navigator.get_location_at_y_offset( cursor_location, height, ) From 5af24a22d4b40516cd03f0e11d64f2a072b09793 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 15:41:05 +0000 Subject: [PATCH 144/150] Making some things public, ensuring types exported --- src/textual/document/_document_navigator.py | 2 +- src/textual/widgets/_text_area.py | 22 ++++++++++----------- src/textual/widgets/text_area.py | 4 ++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index db97b4479e..d736bc0b60 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -18,7 +18,7 @@ class DocumentNavigator: Your cursor visually moves through the wrapped version of the document, rather than the raw document. So, for example, pressing down on the keyboard - may move your cursor to a position further along the current document line, + may move your cursor to a position further along the current raw document line, rather than on to the next line in the raw document. The DocumentNavigator class manages that behaviour. diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 346eaef9b5..0e34fb2166 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -336,7 +336,7 @@ def __init__( self.wrapped_document: WrappedDocument = WrappedDocument(self.document) """The wrapped view of the document.""" - self._navigator: DocumentNavigator = DocumentNavigator(self.wrapped_document) + self.navigator: DocumentNavigator = DocumentNavigator(self.wrapped_document) """Queried to determine where the cursor should move given a navigation action, accounting for wrapping etc.""" @@ -736,7 +736,7 @@ def _set_document(self, text: str, language: str | None) -> None: self.document = document self.wrapped_document = WrappedDocument(document, tab_width=self.indent_width) - self._navigator = DocumentNavigator(self.wrapped_document) + self.navigator = DocumentNavigator(self.wrapped_document) self._build_highlight_map() self.move_cursor((0, 0)) self._rewrap_and_refresh_virtual_size() @@ -1486,7 +1486,7 @@ def get_cursor_left_location(self) -> Location: Returns: The location of the cursor if it moves left. """ - return self._navigator.get_location_left(self.cursor_location) + return self.navigator.get_location_left(self.cursor_location) def action_cursor_right(self, select: bool = False) -> None: """Move the cursor one location to the right. @@ -1505,7 +1505,7 @@ def get_cursor_right_location(self) -> Location: Returns: the location the cursor will move to if it moves right. """ - return self._navigator.get_location_right(self.cursor_location) + return self.navigator.get_location_right(self.cursor_location) def action_cursor_down(self, select: bool = False) -> None: """Move the cursor down one cell. @@ -1522,7 +1522,7 @@ def get_cursor_down_location(self) -> Location: Returns: The location the cursor will move to if it moves down. """ - return self._navigator.get_location_below(self.cursor_location) + return self.navigator.get_location_below(self.cursor_location) def action_cursor_up(self, select: bool = False) -> None: """Move the cursor up one cell. @@ -1539,7 +1539,7 @@ def get_cursor_up_location(self) -> Location: Returns: The location the cursor will move to if it moves up. """ - return self._navigator.get_location_above(self.cursor_location) + return self.navigator.get_location_above(self.cursor_location) def action_cursor_line_end(self, select: bool = False) -> None: """Move the cursor to the end of the line.""" @@ -1552,7 +1552,7 @@ def get_cursor_line_end_location(self) -> Location: Returns: The (row, column) location of the end of the cursors current line. """ - return self._navigator.get_location_end(self.cursor_location) + return self.navigator.get_location_end(self.cursor_location) def action_cursor_line_start(self, select: bool = False) -> None: """Move the cursor to the start of the line.""" @@ -1570,7 +1570,7 @@ def get_cursor_line_start_location(self, smart_home: bool = False) -> Location: Returns: The (row, column) location of the start of the cursors current line. """ - return self._navigator.get_location_home( + return self.navigator.get_location_home( self.cursor_location, smart_home=smart_home ) @@ -1642,7 +1642,7 @@ def action_cursor_page_up(self) -> None: """Move the cursor and scroll up one page.""" height = self.content_size.height _, cursor_location = self.selection - target = self._navigator.get_location_at_y_offset( + target = self.navigator.get_location_at_y_offset( cursor_location, -height, ) @@ -1653,7 +1653,7 @@ def action_cursor_page_down(self) -> None: """Move the cursor and scroll down one page.""" height = self.content_size.height _, cursor_location = self.selection - target = self._navigator.get_location_at_y_offset( + target = self.navigator.get_location_at_y_offset( cursor_location, height, ) @@ -1684,7 +1684,7 @@ def record_cursor_width(self) -> None: cursor_x_offset, _ = self.wrapped_document.location_to_offset( self.cursor_location ) - self._navigator.last_x_offset = cursor_x_offset + self.navigator.last_x_offset = cursor_x_offset # --- Editor operations def insert( diff --git a/src/textual/widgets/text_area.py b/src/textual/widgets/text_area.py index 82a69e38b3..10683d4b09 100644 --- a/src/textual/widgets/text_area.py +++ b/src/textual/widgets/text_area.py @@ -6,8 +6,10 @@ Location, Selection, ) +from textual.document._document_navigator import DocumentNavigator from textual.document._languages import BUILTIN_LANGUAGES from textual.document._syntax_aware_document import SyntaxAwareDocument +from textual.document._wrapped_document import WrappedDocument from textual.widgets._text_area import ( Edit, EndColumn, @@ -22,6 +24,7 @@ "BUILTIN_LANGUAGES", "Document", "DocumentBase", + "DocumentNavigator", "Edit", "EditResult", "EndColumn", @@ -34,4 +37,5 @@ "SyntaxAwareDocument", "TextAreaTheme", "ThemeDoesNotExist", + "WrappedDocument", ] From bf2b9d52722ffd743b987ea653c6f77b1c03338c Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 15:43:37 +0000 Subject: [PATCH 145/150] Fix a mypy complaint --- src/textual/widgets/_text_area.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 0e34fb2166..d57fc23286 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -576,6 +576,7 @@ def _watch_theme(self, theme: str | None) -> None: """We set the styles on this widget when the theme changes, to ensure that if padding is applied, the colours match.""" + theme_object: TextAreaTheme | None if theme is None: # If the theme is None, use the default. theme_object = TextAreaTheme.default() From abcec57c0a9c400870e598b345c8f0faf840003e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 15:47:14 +0000 Subject: [PATCH 146/150] Fixing a few more mypy complaints --- src/textual/_wrap.py | 2 +- src/textual/document/_wrapped_document.py | 2 +- src/textual/expand_tabs.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/_wrap.py b/src/textual/_wrap.py index e47f3fb570..343532794b 100644 --- a/src/textual/_wrap.py +++ b/src/textual/_wrap.py @@ -64,7 +64,7 @@ def compute_wrap_offsets( tab_section_index = 0 cumulative_width = 0 - cumulative_widths = [] # Accumulated tab widths of all codepoints prior (exclusive) + cumulative_widths: list[int] = [] # prefix sum of tab widths for each codepoint record_widths = cumulative_widths.extend for last, (tab_section, tab_width) in loop_last(tab_sections): diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 51feb28482..7ada754ba9 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -197,7 +197,7 @@ def wrap_range( new_wrap_offsets: list[list[int]] = [] new_line_index_to_offsets: list[list[VerticalOffset]] = [] new_offset_to_line_info: list[tuple[LineIndex, SectionOffset]] = [] - new_tab_widths: list[int] = [] + new_tab_widths: list[list[int]] = [] append_wrap_offsets = new_wrap_offsets.append append_tab_widths = new_tab_widths.append diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 0897de3367..7d73228f9c 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -71,7 +71,7 @@ def expand_text_tabs_inline(line: Text, tab_widths: list[int]) -> Text: parts = line.split("\t", include_separator=True) tab_widths_iter = iter(tab_widths) - new_parts = [] + new_parts: list[Text] = [] append_part = new_parts.append for part in parts: if part.plain.endswith("\t"): From 07f64f37d1a8cd416a22ebf094fb45757e92c746 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 23 Jan 2024 16:25:17 +0000 Subject: [PATCH 147/150] Rename function to expand_text_tabs_from_widths and add docstring --- src/textual/expand_tabs.py | 17 ++++++++++++++--- src/textual/widgets/_text_area.py | 4 ++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py index 7d73228f9c..9026a6fabe 100644 --- a/src/textual/expand_tabs.py +++ b/src/textual/expand_tabs.py @@ -61,9 +61,20 @@ def expand_tabs_inline(line: str, tab_size: int = 4) -> str: ) -def expand_text_tabs_inline(line: Text, tab_widths: list[int]) -> Text: - """Expand tabs to the widths defined in the `tab_widths` list, retaining style information - across the expanded tab characters. +def expand_text_tabs_from_widths(line: Text, tab_widths: list[int]) -> Text: + """Expand tabs to the widths defined in the `tab_widths` list. + + This will return a new Text instance with tab characters expanded into a + number of spaces. Each time a tab is encountered, it's expanded into the + next integer encountered in the `tab_widths` list. Consequently, the length + of `tab_widths` should match the number of tab chracters in `line`. + + Args: + line: The `Text` instance to expand tabs in. + tab_widths: The widths to expand tabs to. + + Returns: + A new text instance with tab characters converted to spaces. """ if "\t" not in line.plain: return line diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index d57fc23286..9555963cfb 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -29,7 +29,7 @@ SyntaxAwareDocumentError, ) from textual.document._wrapped_document import WrappedDocument -from textual.expand_tabs import expand_tabs_inline, expand_text_tabs_inline +from textual.expand_tabs import expand_tabs_inline, expand_text_tabs_from_widths if TYPE_CHECKING: from tree_sitter import Language @@ -1024,7 +1024,7 @@ def render_line(self, widget_y: int) -> Strip: section_tab_widths = line_tab_widths[ tabs_before : tabs_before + tabs_within ] - line = expand_text_tabs_inline(line, section_tab_widths) + line = expand_text_tabs_from_widths(line, section_tab_widths) else: line.expand_tabs(self.indent_width) From 39e7a423710fd11b9a71fe8383bfa24076809bfe Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jan 2024 12:03:15 +0000 Subject: [PATCH 148/150] TextArea documentation updates --- docs/widgets/text_area.md | 62 ++++++++++++++++------- src/textual/document/_wrapped_document.py | 2 + 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md index bc3a5e25ad..a8c8cfe24a 100644 --- a/docs/widgets/text_area.md +++ b/docs/widgets/text_area.md @@ -1,17 +1,24 @@ # TextArea -!!! tip "Added in version 0.38.0" +!!! tip "Added in version 0.38.0". Soft wrapping added in version 0.48.0. A widget for editing text which may span multiple lines. -Supports syntax highlighting for a selection of languages. +Supports text selection, soft wrapping, optional syntax highlighting with tree-sitter +and a variety of keybindings. - [x] Focusable - [ ] Container - ## Guide +### Default behavior + +By default, the `TextArea` widget is a standard multi-line input box with soft-wrapping enabled. + +If you're interested in editing code, you may wish to use the [`TextArea.code_editor`] convenience constructor. +This is a method which, by default, returns a new `TextArea` with soft-wrapping disabled, line numbers enabled, and the tab key behavior configured to insert `\t`. + ### Syntax highlighting dependencies To enable syntax highlighting, you'll need to install the `syntax` extra dependencies: @@ -29,7 +36,8 @@ To enable syntax highlighting, you'll need to install the `syntax` extra depende ``` This will install `tree-sitter` and `tree-sitter-languages`. -These packages are distributed as binary wheels, so it may limit your applications ability to run in environments where these wheels are not supported. +These packages are distributed as binary wheels, so it may limit your applications ability to run in environments where these wheels are not available. +After installing, you can set the [`language`][textual.widgets._text_area.TextArea.language] reactive attribute on the `TextArea` to enable highlighting. ### Loading text @@ -46,8 +54,7 @@ In this example we load some initial text into the `TextArea`, and set the langu --8<-- "docs/examples/widgets/text_area_example.py" ``` -To load content into the `TextArea` after it has already been created, -use the [`load_text`][textual.widgets._text_area.TextArea.load_text] method. +To update the content programmatically, set the [`text`][textual.widgets._text_area.TextArea.text] property to a string value. To update the parser used for syntax highlighting, set the [`language`][textual.widgets._text_area.TextArea.language] reactive attribute: @@ -82,7 +89,7 @@ Some other convenient methods are available, such as [`insert`][textual.widgets. #### Moving the cursor The cursor location is available via the [`cursor_location`][textual.widgets._text_area.TextArea.cursor_location] property, which represents -the location of the cursor as a tuple `(row_index, column_index)`. These indices are zero-based. +the location of the cursor as a tuple `(row_index, column_index)`. These indices are zero-based and represent the position of the cursor in the content. Writing a new value to `cursor_location` will immediately update the location of the cursor. ```python @@ -262,6 +269,12 @@ This immediately updates the appearance of the `TextArea`: ```{.textual path="docs/examples/widgets/text_area_custom_theme.py" columns="42" lines="8"} ``` +### Tab behaviour + +Pressing the ++tab++ key will shift focus to the next widget in your application by default. +This matches how other widgets work in Textual. +To have ++tab++ insert a `\t` character, set the `tab_behaviour` attribute to the string value `"indent"`. + ### Indentation The character(s) inserted when you press tab is controlled by setting the `indent_type` attribute to either `tabs` or `spaces`. @@ -315,7 +328,7 @@ Let's extend `TextArea` to add a feature which automatically closes parentheses This intercepts the key handler when `"("` is pressed, and inserts `"()"` instead. It then moves the cursor so that it lands between the open and closing parentheses. -Typing `def hello(` into the `TextArea` results in the bracket automatically being closed: +Typing "`def hello(`" into the `TextArea` now results in the bracket automatically being closed: ```{.textual path="docs/examples/widgets/text_area_extended.py" columns="36" lines="4" press="d,e,f,space,h,e,l,l,o,left_parenthesis"} ``` @@ -430,17 +443,28 @@ If you notice some highlights are missing after registering a language, the issu The names assigned in tree-sitter highlight queries are often reused across multiple languages. For example, `@string` is used in many languages to highlight strings. + +#### Navigation and wrapping information + +If you're building functionality on top of `TextArea`, it may be useful to inspect the `navigator` and `wrapped_document` attributes. + +- `navigator` is a `DocumentNavigator` instance. It can give us general information about the cursor's location within a document, as well as where the cursor will move to when certain actions are performed. +- `wrapped_document` is a `WrappedDocument` it can be used to convert document locations to visual locations, taking wrapping into account. It also offers a variety of other convenience methods and properties. + +A detailed view of these classes is out of scope, but do note that a lot of the functionality of `TextArea` exists within them, so inspecting them could be worthwhile. + ## Reactive attributes -| Name | Type | Default | Description | -|------------------------|--------------------------|--------------------|--------------------------------------------------| -| `language` | `str | None` | `None` | The language to use for syntax highlighting. | -| `theme` | `str | None` | `TextAreaTheme.default()` | The theme to use for syntax highlighting. | -| `selection` | `Selection` | `Selection()` | The current selection. | -| `show_line_numbers` | `bool` | `True` | Show or hide line numbers. | -| `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | -| `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | +| Name | Type | Default | Description | +|------------------------|--------------------------|--------------------|------------------------------------------------------------------| +| `language` | `str | None` | `None` | The language to use for syntax highlighting. | +| `theme` | `str | None` | `TextAreaTheme.default()` | The theme to use for syntax highlighting. | +| `selection` | `Selection` | `Selection()` | The current selection. | +| `show_line_numbers` | `bool` | `True` | Show or hide line numbers. | +| `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | +| `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | | `cursor_blink` | `bool` | `True` | Enable/disable blinking of the cursor when the widget has focus. | +| `soft_wrap` | `bool` | `True` | Enable/disable soft wrapping. | ## Messages @@ -465,8 +489,10 @@ Styling should be done exclusively via [`TextAreaTheme`][textual.widgets.text_ar ## See also -- [`Input`][textual.widgets.Input] - for single-line text input. -- [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme] - for theming the `TextArea`. +- [`Input`][textual.widgets.Input] - single-line text input widget +- [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme] - theming the `TextArea` +- [`DocumentNavigator`][textual.widgets.text_area.DocumentNavigator] - guides cursor movement +- [`WrappedDocument`][textual.widgets.text_area.WrappedDocument] - manages wrapping the document - The tree-sitter documentation [website](https://tree-sitter.github.io/tree-sitter/). - The tree-sitter Python bindings [repository](https://github.com/tree-sitter/py-tree-sitter). - `py-tree-sitter-languages` [repository](https://github.com/grantjenks/py-tree-sitter-languages) (provides binary wheels for a large variety of tree-sitter languages). diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 7ada754ba9..a46b7a78cb 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -135,6 +135,8 @@ def lines(self) -> list[list[str]]: document. The list[str] at each index is the content of the raw document line split into multiple lines via wrapping. + Note that this is expensive to compute and is not cached. + Returns: A list of lines from the wrapped version of the document. """ From a10eaf38cbe678e3000a8b9d06b46012f054eb4d Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 24 Jan 2024 13:29:03 +0000 Subject: [PATCH 149/150] Update defaults in docs --- docs/widgets/text_area.md | 22 +++++++++++----------- src/textual/widgets/_text_area.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md index a8c8cfe24a..f6ceab3d52 100644 --- a/docs/widgets/text_area.md +++ b/docs/widgets/text_area.md @@ -12,7 +12,7 @@ and a variety of keybindings. ## Guide -### Default behavior +### Code editing vs plain text editing By default, the `TextArea` widget is a standard multi-line input box with soft-wrapping enabled. @@ -455,16 +455,16 @@ A detailed view of these classes is out of scope, but do note that a lot of the ## Reactive attributes -| Name | Type | Default | Description | -|------------------------|--------------------------|--------------------|------------------------------------------------------------------| -| `language` | `str | None` | `None` | The language to use for syntax highlighting. | -| `theme` | `str | None` | `TextAreaTheme.default()` | The theme to use for syntax highlighting. | -| `selection` | `Selection` | `Selection()` | The current selection. | -| `show_line_numbers` | `bool` | `True` | Show or hide line numbers. | -| `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | -| `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | -| `cursor_blink` | `bool` | `True` | Enable/disable blinking of the cursor when the widget has focus. | -| `soft_wrap` | `bool` | `True` | Enable/disable soft wrapping. | +| Name | Type | Default | Description | +|------------------------|--------------------------|---------------|------------------------------------------------------------------| +| `language` | `str | None` | `None` | The language to use for syntax highlighting. | +| `theme` | `str | None` | `TextAreaTheme.default()` | The theme to use for syntax highlighting. | +| `selection` | `Selection` | `Selection()` | The current selection. | +| `show_line_numbers` | `bool` | `False` | Show or hide line numbers. | +| `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | +| `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | +| `cursor_blink` | `bool` | `True` | Enable/disable blinking of the cursor when the widget has focus. | +| `soft_wrap` | `bool` | `True` | Enable/disable soft wrapping. | ## Messages diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 9555963cfb..b14ceec761 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -214,7 +214,7 @@ class TextArea(ScrollView, can_focus=True): The text selected in the document is available via the `TextArea.selected_text` property. """ - show_line_numbers: Reactive[bool] = reactive(True, init=False) + show_line_numbers: Reactive[bool] = reactive(False, init=False) """True to show the line number column on the left edge, otherwise False. Changing this value will immediately re-render the `TextArea`.""" From d68e683574fc912356369b62178acb983ae9a3b9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 25 Jan 2024 15:31:32 +0000 Subject: [PATCH 150/150] Addressing pull request feedback --- docs/widgets/text_area.md | 8 +++++--- src/textual/document/_document.py | 5 ----- src/textual/document/_document_navigator.py | 1 - src/textual/widgets/_text_area.py | 9 ++++++--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md index f6ceab3d52..812a3a6327 100644 --- a/docs/widgets/text_area.md +++ b/docs/widgets/text_area.md @@ -1,7 +1,9 @@ # TextArea -!!! tip "Added in version 0.38.0". Soft wrapping added in version 0.48.0. +!!! tip + + Added in version 0.38.0. Soft wrapping added in version 0.48.0. A widget for editing text which may span multiple lines. Supports text selection, soft wrapping, optional syntax highlighting with tree-sitter @@ -448,8 +450,8 @@ If you notice some highlights are missing after registering a language, the issu If you're building functionality on top of `TextArea`, it may be useful to inspect the `navigator` and `wrapped_document` attributes. -- `navigator` is a `DocumentNavigator` instance. It can give us general information about the cursor's location within a document, as well as where the cursor will move to when certain actions are performed. -- `wrapped_document` is a `WrappedDocument` it can be used to convert document locations to visual locations, taking wrapping into account. It also offers a variety of other convenience methods and properties. +- `navigator` is a [`DocumentNavigator`][textual.widgets.text_area.DocumentNavigator] instance which can give us general information about the cursor's location within a document, as well as where the cursor will move to when certain actions are performed. +- `wrapped_document` is a [`WrappedDocument`][textual.widgets.text_area.WrappedDocument] instance which can be used to convert document locations to visual locations, taking wrapping into account. It also offers a variety of other convenience methods and properties. A detailed view of these classes is out of scope, but do note that a lot of the functionality of `TextArea` exists within them, so inspecting them could be worthwhile. diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py index c232146541..c0ded760fa 100644 --- a/src/textual/document/_document.py +++ b/src/textual/document/_document.py @@ -205,9 +205,6 @@ def __init__(self, text: str) -> None: if text.endswith(tuple(VALID_NEWLINES)) or not text: self._lines.append("") - self.edit_count = 0 - """The number of edit operations performed on the document.""" - @property def lines(self) -> list[str]: """Get the document as a list of strings, where each string represents a line. @@ -257,8 +254,6 @@ def replace_range(self, start: Location, end: Location, text: str) -> EditResult The EditResult containing information about the completed replace operation. """ - self.edit_count += 1 - top, bottom = sorted((start, end)) top_row, top_column = top bottom_row, bottom_column = bottom diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index d736bc0b60..e265f03b2a 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -270,7 +270,6 @@ def get_location_above(self, location: Location) -> Location: Returns: The cell above the given location. """ - """Get the location up from the given location in the wrapped document.""" # Get the wrap offsets of the current line. line_index, column_index = location diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index b14ceec761..e676aa9e04 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -777,6 +777,10 @@ def _watch_soft_wrap(self) -> None: @property def wrap_width(self) -> int: + """The width which gets used when the document wraps. + + Accounts for gutter, scrollbars, etc. + """ width, _ = self.scrollable_content_region.size cursor_width = 1 if self.soft_wrap: @@ -860,10 +864,9 @@ def render_line(self, widget_y: int) -> Strip: # If we're beyond the height of the document, render blank lines out_of_bounds = y_offset >= wrapped_document.height - blank_line = Strip.blank(self.size.width) if out_of_bounds: - return blank_line + return Strip.blank(self.size.width) # Get the line corresponding to this offset try: @@ -872,7 +875,7 @@ def render_line(self, widget_y: int) -> Strip: line_info = None if line_info is None: - return blank_line + return Strip.blank(self.size.width) line_index, section_offset = line_info