From 3d1a851a82e301f0f6eff53ac0165b6308e2e83e Mon Sep 17 00:00:00 2001 From: Uzzal Hossain Date: Wed, 17 Nov 2021 10:17:37 +0600 Subject: [PATCH 01/15] Fixed a typo --- src/textual/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/events.py b/src/textual/events.py index 02dafafcf..a4222531c 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -66,7 +66,7 @@ class Load(Event, bubble=False): class Idle(Event, bubble=False): """Sent when there are no more items in the message queue. - This is a psuedo-event in that it is created by the Textual system and doesn't go + This is a pseudo-event in that it is created by the Textual system and doesn't go through the usual message queue. """ From 288e70023d1469429ec77bb23d4854a207956724 Mon Sep 17 00:00:00 2001 From: Adrian de Anda Date: Tue, 23 Nov 2021 23:21:06 -0600 Subject: [PATCH 02/15] Fix couple of typos --- src/textual/app.py | 2 +- src/textual/events.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index 28335bada..93a471dda 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -394,7 +394,7 @@ class App(MessagePump): async def on_event(self, event: events.Event) -> None: # Handle input events that haven't been forwarded - # If the event has been forwaded it may have bubbled up back to the App + # If the event has been forwarded it may have bubbled up back to the App if isinstance(event, events.InputEvent) and not event.is_forwarded: if isinstance(event, events.MouseEvent): # Record current mouse position on App diff --git a/src/textual/events.py b/src/textual/events.py index 02dafafcf..35c3ef6ce 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -237,7 +237,7 @@ class MouseEvent(InputEvent, bubble=True): Args: sender (MessageTarget): The sender of the event. x (int): The relative x coordinate. - y (int): The relative y cootdinate. + y (int): The relative y coordinate. delta_x (int): Change in x since the last message. delta_y (int): Change in y since the last message. button (int): Indexed of the pressed button. From 2f808973aa1c4815438c5bdbaf9fdbccef929e5b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 9 Jan 2022 15:38:44 +0000 Subject: [PATCH 03/15] version bump --- CHANGELOG.md | 5 + poetry.lock | 442 +++++++++++++++++++++++-------------------------- pyproject.toml | 4 +- 3 files changed, 215 insertions(+), 236 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f0edd6c..575952148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.1.14] - 2022-01-09 + +### Changed + +- Updated Rich dependency to 11.X ## [0.1.13] - 2022-01-01 diff --git a/poetry.lock b/poetry.lock index 7a97e0e7b..c6bbec086 100644 --- a/poetry.lock +++ b/poetry.lock @@ -19,32 +19,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "backports.entry-points-selectable" -version = "1.1.0" -description = "Compatibility shim providing selectable entry points for older implementations" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "black" @@ -91,7 +76,7 @@ python-versions = ">=3.6.1" [[package]] name = "click" -version = "8.0.1" +version = "8.0.3" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -122,18 +107,18 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "5.5" +version = "6.2" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.6" [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "distlib" -version = "0.3.2" +version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false @@ -141,15 +126,19 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.0.12" +version = "3.4.2" description = "A platform independent file lock." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "ghp-import" -version = "2.0.1" +version = "2.0.2" description = "Copy your docs directly to the gh-pages branch." category = "dev" optional = false @@ -159,26 +148,26 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["twine", "markdown", "flake8"] +dev = ["twine", "markdown", "flake8", "wheel"] [[package]] name = "identify" -version = "2.2.13" +version = "2.4.2" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.6.1" [package.extras] -license = ["editdistance-s"] +license = ["ukkonen"] [[package]] name = "importlib-metadata" -version = "4.6.4" +version = "4.10.0" description = "Read metadata from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -187,7 +176,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -199,7 +188,7 @@ python-versions = "*" [[package]] name = "jinja2" -version = "3.0.1" +version = "3.0.3" description = "A very fast and expressive template engine." category = "dev" optional = false @@ -213,14 +202,14 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.3.4" +version = "3.3.6" description = "Python implementation of Markdown." category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] testing = ["coverage", "pyyaml"] @@ -243,7 +232,7 @@ python-versions = ">=3.6" [[package]] name = "mkdocs" -version = "1.2.2" +version = "1.2.3" description = "Project documentation with Markdown." category = "dev" optional = false @@ -278,7 +267,7 @@ mkdocs = ">=1.1,<2.0" [[package]] name = "mkdocs-material" -version = "7.2.5" +version = "7.3.0" description = "A Material Design theme for MkDocs" category = "dev" optional = false @@ -293,14 +282,11 @@ pymdown-extensions = ">=7.0" [[package]] name = "mkdocs-material-extensions" -version = "1.0.1" +version = "1.0.3" description = "Extension pack for Python Markdown." category = "dev" optional = false -python-versions = ">=3.5" - -[package.dependencies] -mkdocs-material = ">=5.0.0" +python-versions = ">=3.6" [[package]] name = "mkdocstrings" @@ -355,14 +341,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.0" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" @@ -374,11 +360,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "platformdirs" -version = "2.2.0" +version = "2.4.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] @@ -386,21 +372,22 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.14.0" +version = "2.16.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -417,15 +404,15 @@ virtualenv = ">=20.0.8" [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -444,15 +431,18 @@ Markdown = ">=3.2" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -465,7 +455,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -517,11 +507,11 @@ numpy-style = ["docstring_parser (>=0.7.3,<0.8.0)"] [[package]] name = "pyyaml" -version = "5.4.1" +version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" [[package]] name = "pyyaml-env-tag" @@ -536,7 +526,7 @@ pyyaml = "*" [[package]] name = "rich" -version = "10.16.1" +version = "11.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false @@ -583,14 +573,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "typing-extensions" -version = "3.10.0.0" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" version = "3.10.0.2" @@ -601,51 +583,50 @@ python-versions = "*" [[package]] name = "virtualenv" -version = "20.7.2" +version = "20.13.0" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" -filelock = ">=3.0.0,<4" +filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "watchdog" -version = "2.1.5" +version = "2.1.6" description = "Filesystem events monitoring" category = "dev" optional = false python-versions = ">=3.6" [package.extras] -watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] +watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "zipp" -version = "3.5.0" +version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "4e81046724f8d03d079e07d047f8bb6f14a0889716fac3938647934a0052d834" +content-hash = "cdb4f091bb4090e971acb3f64c73ee65d099ea7cbf76455ac6e7a99cf6552190" [metadata.files] astunparse = [ @@ -657,12 +638,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -"backports.entry-points-selectable" = [ - {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, - {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] black = [ {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, @@ -677,8 +654,8 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -689,89 +666,85 @@ commonmark = [ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] coverage = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] distlib = [ - {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, - {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, + {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, + {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, ] ghp-import = [ - {file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"}, + {file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"}, + {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"}, ] identify = [ - {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"}, - {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"}, + {file = "identify-2.4.2-py2.py3-none-any.whl", hash = "sha256:67c1e66225870dce721228176637a8ef965e8dd58450bcc7592249d0dfc4da6c"}, + {file = "identify-2.4.2.tar.gz", hash = "sha256:93e8ec965e888f2212aa5c24b2b662f4832c39acb1d7196a70ea45acb626a05e"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.6.4-py3-none-any.whl", hash = "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"}, - {file = "importlib_metadata-4.6.4.tar.gz", hash = "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f"}, + {file = "importlib_metadata-4.10.0-py3-none-any.whl", hash = "sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4"}, + {file = "importlib_metadata-4.10.0.tar.gz", hash = "sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] markdown = [ - {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, - {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, + {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, + {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] markupsafe = [ {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, @@ -814,20 +787,20 @@ mergedeep = [ {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] mkdocs = [ - {file = "mkdocs-1.2.2-py3-none-any.whl", hash = "sha256:d019ff8e17ec746afeb54eb9eb4112b5e959597aebc971da46a5c9486137f0ff"}, - {file = "mkdocs-1.2.2.tar.gz", hash = "sha256:a334f5bd98ec960638511366eb8c5abc9c99b9083a0ed2401d8791b112d6b078"}, + {file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"}, + {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, ] mkdocs-autorefs = [ {file = "mkdocs-autorefs-0.2.1.tar.gz", hash = "sha256:b8156d653ed91356e71675ce1fa1186d2b2c2085050012522895c9aa98fca3e5"}, {file = "mkdocs_autorefs-0.2.1-py3-none-any.whl", hash = "sha256:f301b983a34259df90b3fcf7edc234b5e6c7065bd578781e66fd90b8cfbe76be"}, ] mkdocs-material = [ - {file = "mkdocs-material-7.2.5.tar.gz", hash = "sha256:e2a3aa5e20fbdb260d22ec56c01247896c6ae743702e1cd9023fd149a4ae9890"}, - {file = "mkdocs_material-7.2.5-py2.py3-none-any.whl", hash = "sha256:332bafc1584d2d229aa05f7894b4b0f62055fc0d05c96e6ef1785c86ef6e8f91"}, + {file = "mkdocs-material-7.3.0.tar.gz", hash = "sha256:07db0580fa96c3473aee99ec3fb4606a1a5a1e4f4467e64c0cd1ba8da5b6476e"}, + {file = "mkdocs_material-7.3.0-py2.py3-none-any.whl", hash = "sha256:b183c27dc0f44e631bbc32c51057f61a3e2ba8b3c1080e59f944167eeba9ff1d"}, ] mkdocs-material-extensions = [ - {file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"}, - {file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"}, + {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, + {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, ] mkdocstrings = [ {file = "mkdocstrings-0.15.2-py3-none-any.whl", hash = "sha256:8d6cbe64c07ae66739010979ca01d49dd2f64d1a45009f089d217b9cd2a65e36"}, @@ -867,44 +840,44 @@ nodeenv = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] platformdirs = [ - {file = "platformdirs-2.2.0-py3-none-any.whl", hash = "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c"}, - {file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"}, + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ - {file = "pre_commit-2.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"}, - {file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"}, + {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, + {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pymdown-extensions = [ {file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"}, {file = "pymdown_extensions-8.2-py3-none-any.whl", hash = "sha256:141452d8ed61165518f2c923454bf054866b85cf466feedb0eb68f04acdc2560"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, @@ -919,43 +892,47 @@ pytkdocs = [ {file = "pytkdocs-0.11.1.tar.gz", hash = "sha256:1ec7e028fe8361acc1ce909ada4e6beabec28ef31e629618549109e1d58549f0"}, ] pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] 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"}, ] rich = [ - {file = "rich-10.16.1-py3-none-any.whl", hash = "sha256:bbe04dd6ac09e4b00d22cb1051aa127beaf6e16c3d8687b026e96d3fca6aad52"}, - {file = "rich-10.16.1.tar.gz", hash = "sha256:4949e73de321784ef6664ebbc854ac82b20ff60b2865097b93f3b9b41e30da27"}, + {file = "rich-11.0.0-py3-none-any.whl", hash = "sha256:d7a8086aa1fa7e817e3bba544eee4fd82047ef59036313147759c11475f0dafd"}, + {file = "rich-11.0.0.tar.gz", hash = "sha256:c32a8340b21c75931f157466fefe81ae10b92c36a5ea34524dff3767238774a4"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1002,43 +979,40 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] virtualenv = [ - {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"}, - {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"}, + {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, + {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, ] watchdog = [ - {file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f57ce4f7e498278fb2a091f39359930144a0f2f90ea8cbf4523c4e25de34028"}, - {file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b74d0d92a69a7ab5f101f9fe74e44ba017be269efa824337366ccbb4effde85"}, - {file = "watchdog-2.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59767f476cd1f48531bf378f0300565d879688c82da8369ca8c52f633299523c"}, - {file = "watchdog-2.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:814d396859c95598f7576d15bc257c3bd3ba61fa4bc1db7dfc18f09070ded7da"}, - {file = "watchdog-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28777dbed3bbd95f9c70f461443990a36c07dbf49ae7cd69932cdd1b8fb2850c"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5cf78f794c9d7bc64a626ef4f71aff88f57a7ae288e0b359a9c6ea711a41395f"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43bf728eb7830559f329864ab5da2302c15b2efbac24ad84ccc09949ba753c40"}, - {file = "watchdog-2.1.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7053d4d22dc95c5e0c90aeeae1e4ed5269d2f04001798eec43a654a03008d22"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f3ad1d973fe8fc8fe64ba38f6a934b74346342fa98ef08ad5da361a05d46044"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41d44ef21a77a32b55ce9bf59b75777063751f688de51098859b7c7f6466589a"}, - {file = "watchdog-2.1.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed4ca4351cd2bb0d863ee737a2011ca44d8d8be19b43509bd4507f8a449b376b"}, - {file = "watchdog-2.1.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8874d5ad6b7f43b18935d9b0183e29727a623a216693d6938d07dfd411ba462f"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:50a7f81f99d238f72185f481b493f9de80096e046935b60ea78e1276f3d76960"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e40e33a4889382824846b4baa05634e1365b47c6fa40071dc2d06b4d7c715fc1"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_i686.whl", hash = "sha256:78b1514067ff4089f4dac930b043a142997a5b98553120919005e97fbaba6546"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64.whl", hash = "sha256:58ae842300cbfe5e62fb068c83901abe76e4f413234b7bec5446e4275eb1f9cb"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:b0cc7d8b7d60da6c313779d85903ce39a63d89d866014b085f720a083d5f3e9a"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_s390x.whl", hash = "sha256:e60d3bb7166b7cb830b86938d1eb0e6cfe23dfd634cce05c128f8f9967895193"}, - {file = "watchdog-2.1.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:51af09ae937ada0e9a10cc16988ec03c649754a91526170b6839b89fc56d6acb"}, - {file = "watchdog-2.1.5-py3-none-win32.whl", hash = "sha256:9391003635aa783957b9b11175d9802d3272ed67e69ef2e3394c0b6d9d24fa9a"}, - {file = "watchdog-2.1.5-py3-none-win_amd64.whl", hash = "sha256:eab14adfc417c2c983fbcb2c73ef3f28ba6990d1fff45d1180bf7e38bda0d98d"}, - {file = "watchdog-2.1.5-py3-none-win_ia64.whl", hash = "sha256:a2888a788893c4ef7e562861ec5433875b7915f930a5a7ed3d32c048158f1be5"}, - {file = "watchdog-2.1.5.tar.gz", hash = "sha256:5563b005907613430ef3d4aaac9c78600dd5704e84764cb6deda4b3d72807f09"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, + {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, + {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, + {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, + {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, + {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, + {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, + {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, ] zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] diff --git a/pyproject.toml b/pyproject.toml index 5d2fe9c62..17bd50ac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.1.13" +version = "0.1.14" homepage = "https://github.com/willmcgugan/textual" description = "Text User Interface using Rich" authors = ["Will McGugan "] @@ -20,7 +20,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.7" -rich = "^10.7.0" +rich = "^11.0.0" #rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"} typing-extensions = { version = "^3.10.0", python = "<3.8" } From 7f9b6eaefc2fe16b96217c75e58b3b43e28ffedf Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 17 Jan 2022 13:29:49 +0000 Subject: [PATCH 04/15] windows driver abstraction --- src/textual/_windows_driver.py | 12 ++++++++++++ src/textual/app.py | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/textual/_windows_driver.py diff --git a/src/textual/_windows_driver.py b/src/textual/_windows_driver.py new file mode 100644 index 000000000..8d7de90e6 --- /dev/null +++ b/src/textual/_windows_driver.py @@ -0,0 +1,12 @@ +from .driver import Driver + + +class WindowsDriver(Driver): + def start_application_mode(self) -> None: + pass + + def disable_input(self) -> None: + pass + + def stop_application_mode(self) -> None: + pass diff --git a/src/textual/app.py b/src/textual/app.py index 93a471dda..f92e1bfc2 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3,6 +3,7 @@ import os import asyncio from functools import partial +import platform from typing import Any, Callable, ClassVar, Type, TypeVar import warnings @@ -24,7 +25,6 @@ from ._context import active_app from ._event_broker import extract_handler_actions, NoHandler from .driver import Driver from .layouts.dock import DockLayout, Dock -from ._linux_driver import LinuxDriver from .message_pump import MessagePump from ._profile import timer from .view import View @@ -78,7 +78,7 @@ class App(MessagePump): self.console = console or Console() self.error_console = Console(stderr=True) self._screen = screen - self.driver_class = driver_class or LinuxDriver + self.driver_class = driver_class or self.get_driver_class() self._title = title self._layout = DockLayout() self._view_stack: list[DockView] = [] @@ -110,6 +110,24 @@ class App(MessagePump): sub_title: Reactive[str] = Reactive("") background: Reactive[str] = Reactive("black") + def get_driver_class(self) -> Type[Driver]: + """Get a driver class for this platform. + + Called by the constructor. + + Returns: + Driver: A Driver class which manages input and display. + """ + if platform.system() == "Windows": + from ._windows_driver import WindowsDriver + + driver_class = WindowsDriver + else: + from ._linux_driver import LinuxDriver + + driver_class = LinuxDriver + return driver_class + def __rich_repr__(self) -> rich.repr.Result: yield "title", self.title From 54e63428644710112215c4f2d27cd64daeeda6fa Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 19 Jan 2022 16:16:29 +0000 Subject: [PATCH 05/15] windows driver --- examples/animation.py | 2 +- src/textual/_windows_driver.py | 12 - src/textual/app.py | 4 +- src/textual/driver.py | 3 - src/textual/drivers/__init__.py | 0 .../linux_driver.py} | 20 +- src/textual/drivers/win32.py | 355 ++++++++++++++++++ src/textual/drivers/windows_driver.py | 148 ++++++++ 8 files changed, 517 insertions(+), 27 deletions(-) delete mode 100644 src/textual/_windows_driver.py create mode 100644 src/textual/drivers/__init__.py rename src/textual/{_linux_driver.py => drivers/linux_driver.py} (95%) create mode 100644 src/textual/drivers/win32.py create mode 100644 src/textual/drivers/windows_driver.py diff --git a/examples/animation.py b/examples/animation.py index 79edd4817..b6b58e1be 100644 --- a/examples/animation.py +++ b/examples/animation.py @@ -33,4 +33,4 @@ class SmoothApp(App): self.bar.layout_offset_x = -40 -SmoothApp.run(log="textual.log") +SmoothApp.run(log="textual.log", log_verbosity=3) diff --git a/src/textual/_windows_driver.py b/src/textual/_windows_driver.py deleted file mode 100644 index 8d7de90e6..000000000 --- a/src/textual/_windows_driver.py +++ /dev/null @@ -1,12 +0,0 @@ -from .driver import Driver - - -class WindowsDriver(Driver): - def start_application_mode(self) -> None: - pass - - def disable_input(self) -> None: - pass - - def stop_application_mode(self) -> None: - pass diff --git a/src/textual/app.py b/src/textual/app.py index f92e1bfc2..c664f9ff0 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -119,11 +119,11 @@ class App(MessagePump): Driver: A Driver class which manages input and display. """ if platform.system() == "Windows": - from ._windows_driver import WindowsDriver + from .drivers.windows_driver import WindowsDriver driver_class = WindowsDriver else: - from ._linux_driver import LinuxDriver + from .drivers.linux_driver import LinuxDriver driver_class = LinuxDriver return driver_class diff --git a/src/textual/driver.py b/src/textual/driver.py index ed4df782d..d6b1fd5ba 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -14,9 +14,6 @@ if TYPE_CHECKING: from rich.console import Console -WINDOWS = platform.system() == "Windows" - - class Driver(ABC): def __init__(self, console: "Console", target: "MessageTarget") -> None: self.console = console diff --git a/src/textual/drivers/__init__.py b/src/textual/drivers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/textual/_linux_driver.py b/src/textual/drivers/linux_driver.py similarity index 95% rename from src/textual/_linux_driver.py rename to src/textual/drivers/linux_driver.py index a20e19f0e..057688988 100644 --- a/src/textual/_linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -14,17 +14,19 @@ from threading import Event, Thread if TYPE_CHECKING: from rich.console import Console -from . import log +from .. import log -from . import events -from .driver import Driver -from .geometry import Size -from ._types import MessageTarget -from ._xterm_parser import XTermParser -from ._profile import timer +from .. import events +from ..driver import Driver +from ..geometry import Size +from .._types import MessageTarget +from .._xterm_parser import XTermParser +from .._profile import timer class LinuxDriver(Driver): + """Powers display and input for Linux / MacOS""" + def __init__(self, console: "Console", target: "MessageTarget") -> None: super().__init__(console, target) self.fileno = sys.stdin.fileno() @@ -215,11 +217,11 @@ class LinuxDriver(Driver): if __name__ == "__main__": from time import sleep from rich.console import Console - from . import events + from .. import events console = Console() - from .app import App + from ..app import App class MyApp(App): async def on_mount(self, event: events.Mount) -> None: diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py new file mode 100644 index 000000000..a73a05463 --- /dev/null +++ b/src/textual/drivers/win32.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 - 2021 Avram Lubkin, All Rights Reserved + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Support functions and wrappers for calls to the Windows API +""" + +import atexit +import codecs +from collections import namedtuple +import ctypes +from ctypes import wintypes +import io +import msvcrt # pylint: disable=import-error +import os +import platform +import sys + +LPDWORD = ctypes.POINTER(wintypes.DWORD) +COORD = wintypes._COORD # pylint: disable=protected-access + +# Console input modes +ENABLE_ECHO_INPUT = 0x0004 +ENABLE_EXTENDED_FLAGS = 0x0080 +ENABLE_INSERT_MODE = 0x0020 +ENABLE_LINE_INPUT = 0x0002 +ENABLE_MOUSE_INPUT = 0x0010 +ENABLE_PROCESSED_INPUT = 0x0001 +ENABLE_QUICK_EDIT_MODE = 0x0040 +ENABLE_WINDOW_INPUT = 0x0008 +ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 + +# Console output modes +ENABLE_PROCESSED_OUTPUT = 0x0001 +ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 +DISABLE_NEWLINE_AUTO_RETURN = 0x0008 +ENABLE_LVB_GRID_WORLDWIDE = 0x0010 + +if tuple(int(num) for num in platform.version().split(".")) >= ( + 10, + 0, + 10586, +): + VTMODE_SUPPORTED = True + CBREAK_MODE = ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT + RAW_MODE = ENABLE_VIRTUAL_TERMINAL_INPUT +else: + VTMODE_SUPPORTED = False + CBREAK_MODE = ENABLE_PROCESSED_INPUT + RAW_MODE = 0 + +GTS_SUPPORTED = hasattr(os, "get_terminal_size") +TerminalSize = namedtuple("TerminalSize", ("columns", "lines")) + + +class ConsoleScreenBufferInfo( + ctypes.Structure +): # pylint: disable=too-few-public-methods + """ + Python representation of CONSOLE_SCREEN_BUFFER_INFO structure + https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str + """ + + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + + +CSBIP = ctypes.POINTER(ConsoleScreenBufferInfo) + + +def _check_bool(result, func, args): # pylint: disable=unused-argument + """ + Used as an error handler for Windows calls + Gets last error if call is not successful + """ + + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + +KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) + +KERNEL32.GetConsoleCP.errcheck = _check_bool +KERNEL32.GetConsoleCP.argtypes = tuple() + +KERNEL32.GetConsoleMode.errcheck = _check_bool +KERNEL32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD) + +KERNEL32.SetConsoleMode.errcheck = _check_bool +KERNEL32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD) + +KERNEL32.GetConsoleScreenBufferInfo.errcheck = _check_bool +KERNEL32.GetConsoleScreenBufferInfo.argtypes = (wintypes.HANDLE, CSBIP) + + +def get_csbi(filehandle=None): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + + Returns: + :py:class:`ConsoleScreenBufferInfo`: CONSOLE_SCREEN_BUFFER_INFO_ structure + + Wrapper for GetConsoleScreenBufferInfo_ + + If ``filehandle`` is :py:data:`None`, uses the filehandle of :py:data:`sys.__stdout__`. + + """ + + if filehandle is None: + filehandle = msvcrt.get_osfhandle(sys.__stdout__.fileno()) + + csbi = ConsoleScreenBufferInfo() + KERNEL32.GetConsoleScreenBufferInfo(filehandle, ctypes.byref(csbi)) + return csbi + + +def get_console_input_encoding(): + """ + Returns: + int: Current console mode + + Raises: + OSError: Error calling Windows API + + Query for the console input code page and provide an encoding + + If the code page can not be resolved to a Python encoding, :py:data:`None` is returned. + """ + + encoding = "cp%d" % KERNEL32.GetConsoleCP() + + try: + codecs.lookup(encoding) + except LookupError: + return None + + return encoding + + +def get_console_mode(filehandle): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + + Returns: + int: Current console mode + + Raises: + OSError: Error calling Windows API + + Wrapper for GetConsoleMode_ + """ + + mode = wintypes.DWORD() + KERNEL32.GetConsoleMode(filehandle, ctypes.byref(mode)) + return mode.value + + +def set_console_mode(filehandle, mode): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + mode(int): Desired console mode + + Raises: + OSError: Error calling Windows API + + Wrapper for SetConsoleMode_ + """ + + return bool(KERNEL32.SetConsoleMode(filehandle, mode)) + + +def setcbreak(filehandle): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + + Raises: + OSError: Error calling Windows API + + Convenience function which mimics :py:func:`tty.setcbreak` behavior + + All console input options are disabled except ``ENABLE_PROCESSED_INPUT`` + and, if supported, ``ENABLE_VIRTUAL_TERMINAL_INPUT`` + """ + + set_console_mode(filehandle, CBREAK_MODE) + + +def setraw(filehandle): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + + Raises: + OSError: Error calling Windows API + + Convenience function which mimics :py:func:`tty.setraw` behavior + + All console input options are disabled except, if supported, ``ENABLE_VIRTUAL_TERMINAL_INPUT`` + """ + + set_console_mode(filehandle, RAW_MODE) + + +def enable_vt_mode(filehandle=None): + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + + Raises: + OSError: Error calling Windows API + + Enables virtual terminal processing mode for the given console + + If ``filehandle`` is :py:data:`None`, uses the filehandle of :py:data:`sys.__stdout__`. + """ + + if filehandle is None: + filehandle = msvcrt.get_osfhandle(sys.__stdout__.fileno()) + + mode = get_console_mode(filehandle) + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING + set_console_mode(filehandle, mode) + + +def get_terminal_size(fd): # pylint: disable=invalid-name + """ + Args: + fd(int): Python file descriptor + + Returns: + :py:class:`os.terminal_size`: Named tuple representing terminal size + + Convenience function for getting terminal size + + In Python 3.3 and above, this is a wrapper for :py:func:`os.get_terminal_size`. + In older versions of Python, this function calls GetConsoleScreenBufferInfo_. + """ + + # In Python 3.3+ we can let the standard library handle this + if GTS_SUPPORTED: + return os.get_terminal_size(fd) + + handle = msvcrt.get_osfhandle(fd) + window = get_csbi(handle).srWindow + return TerminalSize(window.Right - window.Left + 1, window.Bottom - window.Top + 1) + + +def flush_and_set_console(fd, mode): # pylint: disable=invalid-name + """ + Args: + filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + mode(int): Desired console mode + + Attempts to set console to specified mode, but will not raise on failure + + If the file descriptor is STDOUT or STDERR, attempts to flush first + """ + + try: + if fd in (sys.__stdout__.fileno(), sys.__stderr__.fileno()): + sys.__stdout__.flush() + sys.__stderr__.flush() + except (AttributeError, TypeError, io.UnsupportedOperation): + pass + + try: + filehandle = msvcrt.get_osfhandle(fd) + set_console_mode(filehandle, mode) + except OSError: + pass + + +def get_term(fd, fallback=True): # pylint: disable=invalid-name + """ + Args: + fd(int): Python file descriptor + fallback(bool): Use fallback terminal type if type can not be determined + Returns: + str: Terminal type + + Attempts to determine and enable the current terminal type + + The current logic is: + + - If TERM is defined in the environment, the value is returned + - Else, if ANSICON is defined in the environment, ``'ansicon'`` is returned + - Else, if virtual terminal mode is natively supported, + it is enabled and ``'vtwin10'`` is returned + - Else, if ``fallback`` is ``True``, Ansicon is loaded, and ``'ansicon'`` is returned + - If no other conditions are satisfied, ``'unknown'`` is returned + + This logic may change in the future as additional terminal types are added. + """ + + # First try TERM + term = os.environ.get("TERM", None) + + if term is None: + + # See if ansicon is enabled + if os.environ.get("ANSICON", None): + term = "ansicon" + + # See if Windows Terminal is being used + elif os.environ.get("WT_SESSION", None): + term = "vtwin10" + + # See if the version of Windows supports VTMODE + elif VTMODE_SUPPORTED: + try: + filehandle = msvcrt.get_osfhandle(fd) + mode = get_console_mode(filehandle) + except OSError: + term = "unknown" + else: + atexit.register(flush_and_set_console, fd, mode) + # pylint: disable=unsupported-binary-operation + set_console_mode(filehandle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + term = "vtwin10" + + # Currently falling back to Ansicon for older versions of Windows + elif fallback: + import ansicon # pylint: disable=import-error,import-outside-toplevel + + ansicon.load() + + try: + filehandle = msvcrt.get_osfhandle(fd) + mode = get_console_mode(filehandle) + except OSError: + term = "unknown" + else: + atexit.register(flush_and_set_console, fd, mode) + set_console_mode(filehandle, mode ^ ENABLE_WRAP_AT_EOL_OUTPUT) + term = "ansicon" + + else: + term = "unknown" + + return term diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py new file mode 100644 index 000000000..8ace1caa9 --- /dev/null +++ b/src/textual/drivers/windows_driver.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import asyncio +from codecs import getincrementaldecoder +import msvcrt +import os +import selectors +import signal +import sys +from threading import Event, Thread +from typing import TYPE_CHECKING + +from ..driver import Driver +from ..geometry import Size + +from . import win32 # +from .. import events +from .. import log +from .._types import MessageTarget +from .._xterm_parser import XTermParser + + +if TYPE_CHECKING: + from rich.console import Console + + +class WindowsDriver(Driver): + """Powers display and input for Windows.""" + + def __init__(self, console: "Console", target: "MessageTarget") -> None: + super().__init__(console, target) + self.in_fileno = sys.stdin.fileno() + self.out_fileno = sys.stdout.fileno() + + self.exit_event = Event() + self._key_thread: Thread | None = None + + def _get_terminal_size(self) -> tuple[int, int]: + width, height = win32.get_terminal_size(self.out_fileno) + return (width, height) + + def _enable_mouse_support(self) -> None: + write = self.console.file.write + write("\x1b[?1000h") # SET_VT200_MOUSE + write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE + write("\x1b[?1015h") # SET_VT200_HIGHLIGHT_MOUSE + write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE + + # write("\x1b[?1007h") + self.console.file.flush() + + # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr + # extensions. + + def _disable_mouse_support(self) -> None: + write = self.console.file.write + write("\x1b[?1000l") # + write("\x1b[?1003l") # + write("\x1b[?1015l") + write("\x1b[?1006l") + self.console.file.flush() + + def start_application_mode(self) -> None: + + loop = asyncio.get_event_loop() + + filehandle = msvcrt.get_osfhandle(self.out_fileno) + win32.enable_vt_mode(filehandle) + + self.console.set_alt_screen(True) + self._enable_mouse_support() + self.console.show_cursor(False) + self.console.file.write("\033[?1003h\n") + win32.setraw(msvcrt.get_osfhandle(self.in_fileno)) + + self._key_thread = Thread( + target=self.run_input_thread, args=(asyncio.get_event_loop(),) + ) + + width, height = win32.get_terminal_size(self.out_fileno) + + asyncio.run_coroutine_threadsafe( + self._target.post_message(events.Resize(self._target, Size(width, height))), + loop=loop, + ) + log("starting key thread") + self._key_thread.start() + + def disable_input(self) -> None: + try: + if not self.exit_event.is_set(): + self._disable_mouse_support() + self.exit_event.set() + if self._key_thread is not None: + self._key_thread.join() + except Exception as error: + # TODO: log this + pass + + def stop_application_mode(self) -> None: + self.disable_input() + + with self.console: + self.console.set_alt_screen(False) + self.console.show_cursor(True) + + def run_input_thread(self, loop) -> None: + try: + self._run_input_thread(loop) + except Exception: + pass # TODO: log + + def _run_input_thread(self, loop) -> None: + log("input thread") + + selector = selectors.DefaultSelector() + selector.register(self.in_fileno, selectors.EVENT_READ) + + fileno = self.in_fileno + + def more_data() -> bool: + """Check if there is more data to parse.""" + for key, events in selector.select(0.01): + if events: + return True + return False + + parser = XTermParser(self._target, more_data) + + utf8_decoder = getincrementaldecoder("utf-8")().decode + decode = utf8_decoder + read = os.read + + log("starting thread") + try: + while not self.exit_event.is_set(): + selector_events = selector.select(0.1) + for _selector_key, mask in selector_events: + log(mask) + if mask | selectors.EVENT_READ: + unicode_data = decode(read(fileno, 1024)) + log("ket", unicode_data) + for event in parser.feed(unicode_data): + self.process_event(event) + except Exception as error: + log(error) + finally: + selector.close() From 4118d3eb4581344c5ff76b22b3c2eebd61c133f7 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 20 Jan 2022 16:02:56 +0000 Subject: [PATCH 06/15] win32 support --- src/textual/drivers/windows_driver.py | 100 ++++++++++++++++---------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py index 8ace1caa9..f31ac5363 100644 --- a/src/textual/drivers/windows_driver.py +++ b/src/textual/drivers/windows_driver.py @@ -1,14 +1,17 @@ from __future__ import annotations import asyncio +from ctypes import windll +from ctypes.wintypes import BOOL, DWORD, HANDLE from codecs import getincrementaldecoder + import msvcrt import os import selectors import signal import sys from threading import Event, Thread -from typing import TYPE_CHECKING +from typing import List, Optional, TYPE_CHECKING from ..driver import Driver from ..geometry import Size @@ -22,6 +25,38 @@ from .._xterm_parser import XTermParser if TYPE_CHECKING: from rich.console import Console + from textual.app import App + +WAIT_TIMEOUT = 0x00000102 + + +def wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]: + """ + Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. + Returns `None` on timeout. + http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx + Note that handles should be a list of `HANDLE` objects, not integers. See + this comment in the patch by @quark-zju for the reason why: + ''' Make sure HANDLE on Windows has a correct size + Previously, the type of various HANDLEs are native Python integer + types. The ctypes library will treat them as 4-byte integer when used + in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually + a small integer. Depending on whether the extra 4 bytes are zero-ed out + or not, things can happen to work, or break. ''' + This function returns either `None` or one of the given `HANDLE` objects. + (The return value can be tested with the `is` operator.) + """ + arrtype = HANDLE * len(handles) + handle_array = arrtype(*handles) + + ret: int = windll.kernel32.WaitForMultipleObjects( + len(handle_array), handle_array, BOOL(False), DWORD(timeout) + ) + + if ret == WAIT_TIMEOUT: + return None + else: + return handles[ret] class WindowsDriver(Driver): @@ -64,17 +99,20 @@ class WindowsDriver(Driver): loop = asyncio.get_event_loop() - filehandle = msvcrt.get_osfhandle(self.out_fileno) - win32.enable_vt_mode(filehandle) + win32.enable_vt_mode(msvcrt.get_osfhandle(self.out_fileno)) + win32.setraw(msvcrt.get_osfhandle(self.in_fileno)) self.console.set_alt_screen(True) self._enable_mouse_support() self.console.show_cursor(False) self.console.file.write("\033[?1003h\n") - win32.setraw(msvcrt.get_osfhandle(self.in_fileno)) + + from .._context import active_app + + app = active_app.get() self._key_thread = Thread( - target=self.run_input_thread, args=(asyncio.get_event_loop(),) + target=self.run_input_thread, args=(asyncio.get_event_loop(), app) ) width, height = win32.get_terminal_size(self.out_fileno) @@ -83,7 +121,9 @@ class WindowsDriver(Driver): self._target.post_message(events.Resize(self._target, Size(width, height))), loop=loop, ) - log("starting key thread") + + from .._context import active_app + self._key_thread.start() def disable_input(self) -> None: @@ -104,45 +144,33 @@ class WindowsDriver(Driver): self.console.set_alt_screen(False) self.console.show_cursor(True) - def run_input_thread(self, loop) -> None: + def run_input_thread(self, loop, app: App) -> None: try: - self._run_input_thread(loop) - except Exception: - pass # TODO: log + self._run_input_thread(loop, app) + except Exception as error: + app.log(error) - def _run_input_thread(self, loop) -> None: - log("input thread") + def _run_input_thread(self, loop, app: App) -> None: + app.log("input thread") - selector = selectors.DefaultSelector() - selector.register(self.in_fileno, selectors.EVENT_READ) - - fileno = self.in_fileno - - def more_data() -> bool: - """Check if there is more data to parse.""" - for key, events in selector.select(0.01): - if events: - return True - return False - - parser = XTermParser(self._target, more_data) + parser = XTermParser(self._target, lambda: False) utf8_decoder = getincrementaldecoder("utf-8")().decode decode = utf8_decoder read = os.read - log("starting thread") + input_handle = msvcrt.get_osfhandle(self.in_fileno) + app.log("input_handle", input_handle) + app.log("starting thread") try: while not self.exit_event.is_set(): - selector_events = selector.select(0.1) - for _selector_key, mask in selector_events: - log(mask) - if mask | selectors.EVENT_READ: - unicode_data = decode(read(fileno, 1024)) - log("ket", unicode_data) - for event in parser.feed(unicode_data): - self.process_event(event) + if wait_for_handles([input_handle], 100) is None: + continue + unicode_data = decode(read(self.in_fileno, 1024)) + app.log("key", repr(unicode_data)) + for event in parser.feed(unicode_data): + self.process_event(event) except Exception as error: - log(error) + app.log(error) finally: - selector.close() + app.log("input thread finished") From 988838a872d2c7af6a1113546ace4f15b74a3254 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 27 Jan 2022 16:54:10 +0000 Subject: [PATCH 07/15] working windows driver --- examples/animation.py | 4 +- src/textual/drivers/linux_driver.py | 22 +- src/textual/drivers/win32.py | 483 +++++++++++--------------- src/textual/drivers/windows_driver.py | 54 ++- 4 files changed, 254 insertions(+), 309 deletions(-) diff --git a/examples/animation.py b/examples/animation.py index b6b58e1be..2578834c2 100644 --- a/examples/animation.py +++ b/examples/animation.py @@ -32,5 +32,7 @@ class SmoothApp(App): self.bar.layout_offset_x = -40 + self.set_timer(10, lambda: self.action("quit")) -SmoothApp.run(log="textual.log", log_verbosity=3) + +SmoothApp.run(log="textual.log", log_verbosity=2) diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 057688988..45db1925a 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -157,21 +157,17 @@ class LinuxDriver(Driver): pass def stop_application_mode(self) -> None: + self.disable_input() - with timer("disable_input"): - self.disable_input() + if self.attrs_before is not None: + try: + termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) + except termios.error: + pass - with timer("tcsetattr"): - if self.attrs_before is not None: - try: - termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) - except termios.error: - pass - - with timer("set_alt_screen False, show cursor"): - with self.console: - self.console.set_alt_screen(False) - self.console.show_cursor(True) + with self.console: + self.console.set_alt_screen(False) + self.console.show_cursor(True) def run_input_thread(self, loop) -> None: try: diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py index a73a05463..3455b31bb 100644 --- a/src/textual/drivers/win32.py +++ b/src/textual/drivers/win32.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- -# Copyright 2019 - 2021 Avram Lubkin, All Rights Reserved +from asyncio import AbstractEventLoop, run_coroutine_threadsafe +from codecs import getincrementaldecoder -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -""" -Support functions and wrappers for calls to the Windows API -""" - -import atexit -import codecs -from collections import namedtuple import ctypes -from ctypes import wintypes -import io -import msvcrt # pylint: disable=import-error +from ctypes import byref, Structure, Union, wintypes +from ctypes.wintypes import CHAR, HANDLE, WCHAR, BOOL, WORD, DWORD, SHORT, UINT +import msvcrt import os -import platform import sys +import threading -LPDWORD = ctypes.POINTER(wintypes.DWORD) -COORD = wintypes._COORD # pylint: disable=protected-access +from tkinter.tix import WINDOW +from typing import IO, Callable, List, Optional + +from ..geometry import Size +from ..events import Event, Key, Resize +from .._types import EventTarget +from .._xterm_parser import XTermParser + +KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) # Console input modes ENABLE_ECHO_INPUT = 0x0004 @@ -41,315 +37,248 @@ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ENABLE_LVB_GRID_WORLDWIDE = 0x0010 -if tuple(int(num) for num in platform.version().split(".")) >= ( - 10, - 0, - 10586, -): - VTMODE_SUPPORTED = True - CBREAK_MODE = ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT - RAW_MODE = ENABLE_VIRTUAL_TERMINAL_INPUT -else: - VTMODE_SUPPORTED = False - CBREAK_MODE = ENABLE_PROCESSED_INPUT - RAW_MODE = 0 +STD_INPUT_HANDLE = -10 +STD_OUTPUT_HANDLE = -11 -GTS_SUPPORTED = hasattr(os, "get_terminal_size") -TerminalSize = namedtuple("TerminalSize", ("columns", "lines")) +WAIT_TIMEOUT = 0x00000102 + +GetStdHandle = KERNEL32.GetStdHandle +GetStdHandle.argtypes = [wintypes.DWORD] +GetStdHandle.restype = wintypes.HANDLE -class ConsoleScreenBufferInfo( - ctypes.Structure -): # pylint: disable=too-few-public-methods - """ - Python representation of CONSOLE_SCREEN_BUFFER_INFO structure - https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str - """ +class COORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/coord-str""" _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", wintypes.WORD), - ("srWindow", wintypes.SMALL_RECT), - ("dwMaximumWindowSize", COORD), + ("X", SHORT), + ("Y", SHORT), ] -CSBIP = ctypes.POINTER(ConsoleScreenBufferInfo) +class uChar(Union): + """https://docs.microsoft.com/en-us/windows/console/key-event-record-str""" + + _fields_ = [ + ("AsciiChar", CHAR), + ("UnicodeChar", WCHAR), + ] -def _check_bool(result, func, args): # pylint: disable=unused-argument - """ - Used as an error handler for Windows calls - Gets last error if call is not successful - """ +class KEY_EVENT_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/key-event-record-str""" - if not result: - raise ctypes.WinError(ctypes.get_last_error()) - return args + _fields_ = [ + ("bKeyDown", BOOL), + ("wRepeatCount", WORD), + ("wVirtualKeyCode", WORD), + ("wVirtualScanCode", WORD), + ("uChar", uChar), + ("dwControlKeyState", DWORD), + ] -KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) +class MOUSE_EVENT_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str""" -KERNEL32.GetConsoleCP.errcheck = _check_bool -KERNEL32.GetConsoleCP.argtypes = tuple() - -KERNEL32.GetConsoleMode.errcheck = _check_bool -KERNEL32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD) - -KERNEL32.SetConsoleMode.errcheck = _check_bool -KERNEL32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD) - -KERNEL32.GetConsoleScreenBufferInfo.errcheck = _check_bool -KERNEL32.GetConsoleScreenBufferInfo.argtypes = (wintypes.HANDLE, CSBIP) + _fields_ = [ + ("dwMousePosition", COORD), + ("dwButtonState", DWORD), + ("dwControlKeyState", DWORD), + ("dwEventFlags", DWORD), + ] -def get_csbi(filehandle=None): - """ +class WINDOW_BUFFER_SIZE_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str""" + + _fields_ = [("dwSize", COORD)] + + +class MENU_EVENT_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/menu-event-record-str""" + + _fields_ = [("dwCommandId", UINT)] + + +class FOCUS_EVENT_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/focus-event-record-str""" + + _fields_ = [("bSetFocus", BOOL)] + + +class InputEvent(Union): + """https://docs.microsoft.com/en-us/windows/console/input-record-str""" + + _fields_ = [ + ("KeyEvent", KEY_EVENT_RECORD), + ("MouseEvent", MOUSE_EVENT_RECORD), + ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD), + ("MenuEvent", MENU_EVENT_RECORD), + ("FocusEvent", FOCUS_EVENT_RECORD), + ] + + +class INPUT_RECORD(Structure): + """https://docs.microsoft.com/en-us/windows/console/input-record-str""" + + _fields_ = [("EventType", wintypes.WORD), ("Event", InputEvent)] + + +def _set_console_mode(file: IO, mode: int) -> bool: + """Set the console mode for a given file (stdout or stdin). + Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + file (IO): A file like object. + mode (int): New mode. Returns: - :py:class:`ConsoleScreenBufferInfo`: CONSOLE_SCREEN_BUFFER_INFO_ structure - - Wrapper for GetConsoleScreenBufferInfo_ - - If ``filehandle`` is :py:data:`None`, uses the filehandle of :py:data:`sys.__stdout__`. - + bool: True on success, otherwise False. """ - - if filehandle is None: - filehandle = msvcrt.get_osfhandle(sys.__stdout__.fileno()) - - csbi = ConsoleScreenBufferInfo() - KERNEL32.GetConsoleScreenBufferInfo(filehandle, ctypes.byref(csbi)) - return csbi + windows_filehandle = msvcrt.get_osfhandle(file.fileno()) + success = KERNEL32.SetConsoleMode(windows_filehandle, mode) + return success -def get_console_input_encoding(): - """ - Returns: - int: Current console mode +def _get_console_mode(file: IO) -> int: + """Get the console mode for a given file (stdout or stdin) - Raises: - OSError: Error calling Windows API - - Query for the console input code page and provide an encoding - - If the code page can not be resolved to a Python encoding, :py:data:`None` is returned. - """ - - encoding = "cp%d" % KERNEL32.GetConsoleCP() - - try: - codecs.lookup(encoding) - except LookupError: - return None - - return encoding - - -def get_console_mode(filehandle): - """ Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` + file (IO): A file-like object. Returns: - int: Current console mode - - Raises: - OSError: Error calling Windows API - - Wrapper for GetConsoleMode_ + int: The current console mode. """ - + windows_filehandle = msvcrt.get_osfhandle(file.fileno()) mode = wintypes.DWORD() - KERNEL32.GetConsoleMode(filehandle, ctypes.byref(mode)) + KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode)) return mode.value -def set_console_mode(filehandle, mode): - """ - Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` - mode(int): Desired console mode - - Raises: - OSError: Error calling Windows API - - Wrapper for SetConsoleMode_ - """ - - return bool(KERNEL32.SetConsoleMode(filehandle, mode)) - - -def setcbreak(filehandle): - """ - Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` - - Raises: - OSError: Error calling Windows API - - Convenience function which mimics :py:func:`tty.setcbreak` behavior - - All console input options are disabled except ``ENABLE_PROCESSED_INPUT`` - and, if supported, ``ENABLE_VIRTUAL_TERMINAL_INPUT`` - """ - - set_console_mode(filehandle, CBREAK_MODE) - - -def setraw(filehandle): - """ - Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` - - Raises: - OSError: Error calling Windows API - - Convenience function which mimics :py:func:`tty.setraw` behavior - - All console input options are disabled except, if supported, ``ENABLE_VIRTUAL_TERMINAL_INPUT`` - """ - - set_console_mode(filehandle, RAW_MODE) - - -def enable_vt_mode(filehandle=None): - """ - Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` - - Raises: - OSError: Error calling Windows API - - Enables virtual terminal processing mode for the given console - - If ``filehandle`` is :py:data:`None`, uses the filehandle of :py:data:`sys.__stdout__`. - """ - - if filehandle is None: - filehandle = msvcrt.get_osfhandle(sys.__stdout__.fileno()) - - mode = get_console_mode(filehandle) - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING - set_console_mode(filehandle, mode) - - -def get_terminal_size(fd): # pylint: disable=invalid-name - """ - Args: - fd(int): Python file descriptor +def enable_application_mode() -> Callable[[], None]: + """Enable application mode. Returns: - :py:class:`os.terminal_size`: Named tuple representing terminal size - - Convenience function for getting terminal size - - In Python 3.3 and above, this is a wrapper for :py:func:`os.get_terminal_size`. - In older versions of Python, this function calls GetConsoleScreenBufferInfo_. + Callable[[], None]: A callable that will restore terminal to previous state. """ - # In Python 3.3+ we can let the standard library handle this - if GTS_SUPPORTED: - return os.get_terminal_size(fd) + terminal_in = sys.stdin + terminal_out = sys.stdout - handle = msvcrt.get_osfhandle(fd) - window = get_csbi(handle).srWindow - return TerminalSize(window.Right - window.Left + 1, window.Bottom - window.Top + 1) + current_console_mode_in = _get_console_mode(terminal_in) + current_console_mode_out = _get_console_mode(terminal_out) + + def restore() -> None: + """Restore console mode to previous settings""" + _set_console_mode(terminal_in, current_console_mode_in) + _set_console_mode(terminal_out, current_console_mode_out) + + _set_console_mode( + terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING + ) + _set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT) + return restore -def flush_and_set_console(fd, mode): # pylint: disable=invalid-name +def _wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]: """ - Args: - filehandle(int): Windows filehandle object as returned by :py:func:`msvcrt.get_osfhandle` - mode(int): Desired console mode - - Attempts to set console to specified mode, but will not raise on failure - - If the file descriptor is STDOUT or STDERR, attempts to flush first + Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. + Returns `None` on timeout. + http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx + Note that handles should be a list of `HANDLE` objects, not integers. See + this comment in the patch by @quark-zju for the reason why: + ''' Make sure HANDLE on Windows has a correct size + Previously, the type of various HANDLEs are native Python integer + types. The ctypes library will treat them as 4-byte integer when used + in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually + a small integer. Depending on whether the extra 4 bytes are zero-ed out + or not, things can happen to work, or break. ''' + This function returns either `None` or one of the given `HANDLE` objects. + (The return value can be tested with the `is` operator.) """ + arrtype = HANDLE * len(handles) + handle_array = arrtype(*handles) - try: - if fd in (sys.__stdout__.fileno(), sys.__stderr__.fileno()): - sys.__stdout__.flush() - sys.__stderr__.flush() - except (AttributeError, TypeError, io.UnsupportedOperation): - pass + ret: int = KERNEL32.WaitForMultipleObjects( + len(handle_array), handle_array, BOOL(False), DWORD(timeout) + ) - try: - filehandle = msvcrt.get_osfhandle(fd) - set_console_mode(filehandle, mode) - except OSError: - pass + if ret == WAIT_TIMEOUT: + return None + else: + return handles[ret] -def get_term(fd, fallback=True): # pylint: disable=invalid-name - """ - Args: - fd(int): Python file descriptor - fallback(bool): Use fallback terminal type if type can not be determined - Returns: - str: Terminal type +class EventMonitor(threading.Thread): + """A thread to send key / window events to Textual loop.""" - Attempts to determine and enable the current terminal type + def __init__( + self, + loop: AbstractEventLoop, + app, + target: EventTarget, + exit_event: threading.Event, + process_event: Callable[[Event], None], + ) -> None: + self.loop = loop + self.app = app + self.target = target + self.exit_event = exit_event + self.process_event = process_event + self.app.log("event monitor constructed") + super().__init__() - The current logic is: + def run(self) -> None: + self.app.log("event monitor thread started") + exit_requested = self.exit_event.is_set + parser = XTermParser(self.target, lambda: False) - - If TERM is defined in the environment, the value is returned - - Else, if ANSICON is defined in the environment, ``'ansicon'`` is returned - - Else, if virtual terminal mode is natively supported, - it is enabled and ``'vtwin10'`` is returned - - Else, if ``fallback`` is ``True``, Ansicon is loaded, and ``'ansicon'`` is returned - - If no other conditions are satisfied, ``'unknown'`` is returned + try: + read_count = wintypes.DWORD(0) + hIn = GetStdHandle(STD_INPUT_HANDLE) - This logic may change in the future as additional terminal types are added. - """ + MAX_EVENTS = 1024 + KEY_EVENT = 0x0001 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 - # First try TERM - term = os.environ.get("TERM", None) + arrtype = INPUT_RECORD * MAX_EVENTS + input_records = arrtype() + ReadConsoleInputW = KERNEL32.ReadConsoleInputW + keys: List[str] = [] - if term is None: + while not exit_requested(): + if _wait_for_handles([hIn], 100) is None: + continue + del keys[:] + ReadConsoleInputW( + hIn, byref(input_records), MAX_EVENTS, byref(read_count) + ) + read_input_records = input_records[: read_count.value] - # See if ansicon is enabled - if os.environ.get("ANSICON", None): - term = "ansicon" + apppend_key = keys.append + new_size: Optional[tuple[int, int]] = None + for input_record in read_input_records: + event_type = input_record.EventType + if event_type == KEY_EVENT: + key_event = input_record.Event.KeyEvent + key = key_event.uChar.UnicodeChar + if key_event.bKeyDown or key == "\x1b": + apppend_key(key) + elif event_type == WINDOW_BUFFER_SIZE_EVENT: + size = input_record.Event.WindowBufferSizeEvent.dwSize + new_size = (size.X, size.Y) - # See if Windows Terminal is being used - elif os.environ.get("WT_SESSION", None): - term = "vtwin10" + if keys: + for event in parser.feed("".join(keys)): + self.process_event(event) + if new_size is not None: + self.on_size_change(*new_size) - # See if the version of Windows supports VTMODE - elif VTMODE_SUPPORTED: - try: - filehandle = msvcrt.get_osfhandle(fd) - mode = get_console_mode(filehandle) - except OSError: - term = "unknown" - else: - atexit.register(flush_and_set_console, fd, mode) - # pylint: disable=unsupported-binary-operation - set_console_mode(filehandle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - term = "vtwin10" + except Exception as error: + self.app.log("EVENT MONITOR ERROR", error) + self.app.log("event monitor thread finished") - # Currently falling back to Ansicon for older versions of Windows - elif fallback: - import ansicon # pylint: disable=import-error,import-outside-toplevel - - ansicon.load() - - try: - filehandle = msvcrt.get_osfhandle(fd) - mode = get_console_mode(filehandle) - except OSError: - term = "unknown" - else: - atexit.register(flush_and_set_console, fd, mode) - set_console_mode(filehandle, mode ^ ENABLE_WRAP_AT_EOL_OUTPUT) - term = "ansicon" - - else: - term = "unknown" - - return term + def on_size_change(self, width: int, height: int) -> None: + """Called when terminal size changes.""" + event = Resize(self.target, Size(width, height)) + run_coroutine_threadsafe(self.target.post_message(event), loop=self.loop) diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py index f31ac5363..743d35b11 100644 --- a/src/textual/drivers/windows_driver.py +++ b/src/textual/drivers/windows_driver.py @@ -7,11 +7,9 @@ from codecs import getincrementaldecoder import msvcrt import os -import selectors -import signal import sys from threading import Event, Thread -from typing import List, Optional, TYPE_CHECKING +from typing import Callable, List, Optional, TYPE_CHECKING from ..driver import Driver from ..geometry import Size @@ -69,10 +67,8 @@ class WindowsDriver(Driver): self.exit_event = Event() self._key_thread: Thread | None = None - - def _get_terminal_size(self) -> tuple[int, int]: - width, height = win32.get_terminal_size(self.out_fileno) - return (width, height) + self._event_thread: Thread | None = None + self._restore_console: Callable[[], None] | None = None def _enable_mouse_support(self) -> None: write = self.console.file.write @@ -99,8 +95,7 @@ class WindowsDriver(Driver): loop = asyncio.get_event_loop() - win32.enable_vt_mode(msvcrt.get_osfhandle(self.out_fileno)) - win32.setraw(msvcrt.get_osfhandle(self.in_fileno)) + self._restore_console = win32.enable_application_mode() self.console.set_alt_screen(True) self._enable_mouse_support() @@ -111,11 +106,13 @@ class WindowsDriver(Driver): app = active_app.get() - self._key_thread = Thread( - target=self.run_input_thread, args=(asyncio.get_event_loop(), app) + # self._key_thread = Thread( + # target=self.run_input_thread, args=(asyncio.get_event_loop(), app) + # ) + self._event_thread = win32.EventMonitor( + loop, app, self._target, self.exit_event, self.process_event ) - - width, height = win32.get_terminal_size(self.out_fileno) + width, height = os.get_terminal_size(self.out_fileno) asyncio.run_coroutine_threadsafe( self._target.post_message(events.Resize(self._target, Size(width, height))), @@ -124,22 +121,26 @@ class WindowsDriver(Driver): from .._context import active_app - self._key_thread.start() + # self._key_thread.start() + + self._event_thread.start() def disable_input(self) -> None: try: if not self.exit_event.is_set(): self._disable_mouse_support() self.exit_event.set() - if self._key_thread is not None: - self._key_thread.join() + if self._event_thread is not None: + self._event_thread.join() + self._event_thread = None except Exception as error: # TODO: log this pass def stop_application_mode(self) -> None: self.disable_input() - + if self._restore_console: + self._restore_console() with self.console: self.console.set_alt_screen(False) self.console.show_cursor(True) @@ -162,12 +163,29 @@ class WindowsDriver(Driver): input_handle = msvcrt.get_osfhandle(self.in_fileno) app.log("input_handle", input_handle) app.log("starting thread") + import time + + terminal_size = os.get_terminal_size(self.out_fileno) + import shutil + try: while not self.exit_event.is_set(): + + new_terminal_size = os.get_terminal_size(self.out_fileno) + + if new_terminal_size != terminal_size: + app.log("SIZE CHANGE", new_terminal_size) + terminal_size = new_terminal_size + width, height = new_terminal_size + event = events.Resize(self._target, Size(width, height)) + app.log(event) + self.console.size = (width, height) + self.send_event(event) + if wait_for_handles([input_handle], 100) is None: continue unicode_data = decode(read(self.in_fileno, 1024)) - app.log("key", repr(unicode_data)) + # app.log("key", repr(unicode_data)) for event in parser.feed(unicode_data): self.process_event(event) except Exception as error: From b4358f887b8ddb9592693d61f07bd7bdd4a7789a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 28 Jan 2022 10:13:30 +0000 Subject: [PATCH 08/15] re-construct console --- CHANGELOG.md | 6 ++++++ examples/animation.py | 2 +- src/textual/app.py | 10 +++++++--- src/textual/drivers/win32.py | 4 +--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 575952148..8feba707c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.1.15] - Unreleased + +### Added + +- Added Windows Driver + ## [1.1.14] - 2022-01-09 ### Changed diff --git a/examples/animation.py b/examples/animation.py index 2578834c2..bf5f9ae8e 100644 --- a/examples/animation.py +++ b/examples/animation.py @@ -32,7 +32,7 @@ class SmoothApp(App): self.bar.layout_offset_x = -40 - self.set_timer(10, lambda: self.action("quit")) + # self.set_timer(10, lambda: self.action("quit")) SmoothApp.run(log="textual.log", log_verbosity=2) diff --git a/src/textual/app.py b/src/textual/app.py index c664f9ff0..4c6f58873 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -2,7 +2,6 @@ from __future__ import annotations import os import asyncio -from functools import partial import platform from typing import Any, Callable, ClassVar, Type, TypeVar import warnings @@ -31,6 +30,8 @@ from .view import View from .views import DockView from .widget import Widget, Reactive +PLATFORM = platform.system() +WINDOWS = PLATFORM == "Windows" # asyncio will warn against resources not being cleared warnings.simplefilter("always", ResourceWarning) @@ -118,7 +119,7 @@ class App(MessagePump): Returns: Driver: A Driver class which manages input and display. """ - if platform.system() == "Windows": + if WINDOWS: from .drivers.windows_driver import WindowsDriver driver_class = WindowsDriver @@ -302,6 +303,7 @@ class App(MessagePump): self.console.print_exception() else: try: + self.console = Console() self.title = self._title self.refresh() await self.animator.start() @@ -344,7 +346,9 @@ class App(MessagePump): await self.close_messages() def refresh(self, repaint: bool = True, layout: bool = False) -> None: - sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal" + sync_available = ( + os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal" and not WINDOWS + ) if not self._closed: console = self.console try: diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py index 3455b31bb..55ca6cfe2 100644 --- a/src/textual/drivers/win32.py +++ b/src/textual/drivers/win32.py @@ -1,5 +1,4 @@ from asyncio import AbstractEventLoop, run_coroutine_threadsafe -from codecs import getincrementaldecoder import ctypes from ctypes import byref, Structure, Union, wintypes @@ -9,11 +8,10 @@ import os import sys import threading -from tkinter.tix import WINDOW from typing import IO, Callable, List, Optional from ..geometry import Size -from ..events import Event, Key, Resize +from ..events import Event, Resize from .._types import EventTarget from .._xterm_parser import XTermParser From ffcbe4ba1e04f0bace7d7e8b87aa2e0390555b00 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 28 Jan 2022 11:21:14 +0000 Subject: [PATCH 09/15] discard console param --- src/textual/app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index 4c6f58873..0000f068c 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -61,7 +61,6 @@ class App(MessagePump): def __init__( self, - console: Console | None = None, screen: bool = True, driver_class: Type[Driver] | None = None, log: str = "", @@ -76,7 +75,7 @@ class App(MessagePump): driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None. title (str, optional): Title of the application. Defaults to "Textual Application". """ - self.console = console or Console() + self.console = Console() self.error_console = Console(stderr=True) self._screen = screen self.driver_class = driver_class or self.get_driver_class() From 32a3bea47fc15e1f0f1a7e1560abecd150fbe016 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 28 Jan 2022 11:29:55 +0000 Subject: [PATCH 10/15] tidy and comment --- src/textual/drivers/win32.py | 31 ++++--- src/textual/drivers/windows_driver.py | 125 ++------------------------ 2 files changed, 25 insertions(+), 131 deletions(-) diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py index 55ca6cfe2..66c31540f 100644 --- a/src/textual/drivers/win32.py +++ b/src/textual/drivers/win32.py @@ -1,19 +1,16 @@ -from asyncio import AbstractEventLoop, run_coroutine_threadsafe - import ctypes -from ctypes import byref, Structure, Union, wintypes -from ctypes.wintypes import CHAR, HANDLE, WCHAR, BOOL, WORD, DWORD, SHORT, UINT import msvcrt -import os import sys import threading - +from asyncio import AbstractEventLoop, run_coroutine_threadsafe +from ctypes import Structure, Union, byref, wintypes +from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD from typing import IO, Callable, List, Optional -from ..geometry import Size -from ..events import Event, Resize from .._types import EventTarget from .._xterm_parser import XTermParser +from ..events import Event, Resize +from ..geometry import Size KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True) @@ -243,33 +240,43 @@ class EventMonitor(threading.Thread): input_records = arrtype() ReadConsoleInputW = KERNEL32.ReadConsoleInputW keys: List[str] = [] + append_key = keys.append while not exit_requested(): - if _wait_for_handles([hIn], 100) is None: + # Wait for new events + if _wait_for_handles([hIn], 200) is None: + # No new events continue - del keys[:] + + # Get new events ReadConsoleInputW( hIn, byref(input_records), MAX_EVENTS, byref(read_count) ) read_input_records = input_records[: read_count.value] - apppend_key = keys.append + del keys[:] new_size: Optional[tuple[int, int]] = None + for input_record in read_input_records: event_type = input_record.EventType + if event_type == KEY_EVENT: + # Key event, store unicode char in keys list key_event = input_record.Event.KeyEvent key = key_event.uChar.UnicodeChar if key_event.bKeyDown or key == "\x1b": - apppend_key(key) + append_key(key) elif event_type == WINDOW_BUFFER_SIZE_EVENT: + # Window size changed, store size size = input_record.Event.WindowBufferSizeEvent.dwSize new_size = (size.X, size.Y) if keys: + # Process keys for event in parser.feed("".join(keys)): self.process_event(event) if new_size is not None: + # Process changed size self.on_size_change(*new_size) except Exception as error: diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py index 743d35b11..bc7a73c57 100644 --- a/src/textual/drivers/windows_driver.py +++ b/src/textual/drivers/windows_driver.py @@ -1,60 +1,17 @@ from __future__ import annotations import asyncio -from ctypes import windll -from ctypes.wintypes import BOOL, DWORD, HANDLE -from codecs import getincrementaldecoder - -import msvcrt -import os import sys from threading import Event, Thread -from typing import Callable, List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Callable -from ..driver import Driver -from ..geometry import Size - -from . import win32 # -from .. import events -from .. import log +from .._context import active_app from .._types import MessageTarget -from .._xterm_parser import XTermParser - +from ..driver import Driver +from . import win32 if TYPE_CHECKING: from rich.console import Console - from textual.app import App - -WAIT_TIMEOUT = 0x00000102 - - -def wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]: - """ - Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. - Returns `None` on timeout. - http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx - Note that handles should be a list of `HANDLE` objects, not integers. See - this comment in the patch by @quark-zju for the reason why: - ''' Make sure HANDLE on Windows has a correct size - Previously, the type of various HANDLEs are native Python integer - types. The ctypes library will treat them as 4-byte integer when used - in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually - a small integer. Depending on whether the extra 4 bytes are zero-ed out - or not, things can happen to work, or break. ''' - This function returns either `None` or one of the given `HANDLE` objects. - (The return value can be tested with the `is` operator.) - """ - arrtype = HANDLE * len(handles) - handle_array = arrtype(*handles) - - ret: int = windll.kernel32.WaitForMultipleObjects( - len(handle_array), handle_array, BOOL(False), DWORD(timeout) - ) - - if ret == WAIT_TIMEOUT: - return None - else: - return handles[ret] class WindowsDriver(Driver): @@ -66,7 +23,6 @@ class WindowsDriver(Driver): self.out_fileno = sys.stdout.fileno() self.exit_event = Event() - self._key_thread: Thread | None = None self._event_thread: Thread | None = None self._restore_console: Callable[[], None] | None = None @@ -76,17 +32,12 @@ class WindowsDriver(Driver): write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE write("\x1b[?1015h") # SET_VT200_HIGHLIGHT_MOUSE write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE - - # write("\x1b[?1007h") self.console.file.flush() - # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr - # extensions. - def _disable_mouse_support(self) -> None: write = self.console.file.write - write("\x1b[?1000l") # - write("\x1b[?1003l") # + write("\x1b[?1000l") + write("\x1b[?1003l") write("\x1b[?1015l") write("\x1b[?1006l") self.console.file.flush() @@ -102,27 +53,11 @@ class WindowsDriver(Driver): self.console.show_cursor(False) self.console.file.write("\033[?1003h\n") - from .._context import active_app - app = active_app.get() - # self._key_thread = Thread( - # target=self.run_input_thread, args=(asyncio.get_event_loop(), app) - # ) self._event_thread = win32.EventMonitor( loop, app, self._target, self.exit_event, self.process_event ) - width, height = os.get_terminal_size(self.out_fileno) - - asyncio.run_coroutine_threadsafe( - self._target.post_message(events.Resize(self._target, Size(width, height))), - loop=loop, - ) - - from .._context import active_app - - # self._key_thread.start() - self._event_thread.start() def disable_input(self) -> None: @@ -144,51 +79,3 @@ class WindowsDriver(Driver): with self.console: self.console.set_alt_screen(False) self.console.show_cursor(True) - - def run_input_thread(self, loop, app: App) -> None: - try: - self._run_input_thread(loop, app) - except Exception as error: - app.log(error) - - def _run_input_thread(self, loop, app: App) -> None: - app.log("input thread") - - parser = XTermParser(self._target, lambda: False) - - utf8_decoder = getincrementaldecoder("utf-8")().decode - decode = utf8_decoder - read = os.read - - input_handle = msvcrt.get_osfhandle(self.in_fileno) - app.log("input_handle", input_handle) - app.log("starting thread") - import time - - terminal_size = os.get_terminal_size(self.out_fileno) - import shutil - - try: - while not self.exit_event.is_set(): - - new_terminal_size = os.get_terminal_size(self.out_fileno) - - if new_terminal_size != terminal_size: - app.log("SIZE CHANGE", new_terminal_size) - terminal_size = new_terminal_size - width, height = new_terminal_size - event = events.Resize(self._target, Size(width, height)) - app.log(event) - self.console.size = (width, height) - self.send_event(event) - - if wait_for_handles([input_handle], 100) is None: - continue - unicode_data = decode(read(self.in_fileno, 1024)) - # app.log("key", repr(unicode_data)) - for event in parser.feed(unicode_data): - self.process_event(event) - except Exception as error: - app.log(error) - finally: - app.log("input thread finished") From 0e1ce3ab52d95c2c343481e03a1eae15865dc890 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 28 Jan 2022 11:37:06 +0000 Subject: [PATCH 11/15] fix console --- src/textual/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/app.py b/src/textual/app.py index 0000f068c..95be8c07b 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -200,7 +200,7 @@ class App(MessagePump): """ async def run_app() -> None: - app = cls(console=console, screen=screen, driver_class=driver, **kwargs) + app = cls(screen=screen, driver_class=driver, **kwargs) await app.process_messages() asyncio.run(run_app()) From 1b8d7d184e10889002425641222702afba508aea Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 28 Jan 2022 16:14:13 +0000 Subject: [PATCH 12/15] version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 17bd50ac5..60d699196 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.1.14" +version = "0.1.15" homepage = "https://github.com/willmcgugan/textual" description = "Text User Interface using Rich" authors = ["Will McGugan "] From b21a01dfe166fc7ea1f4827dc35a0993122d4be0 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 30 Jan 2022 14:30:53 +0000 Subject: [PATCH 13/15] Warning about changes --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b3f02849..3dadeb019 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ ![screenshot](./imgs/textual.png) -Textual is a TUI (Text User Interface) framework for Python inspired by modern web development. +Textual is a TUI (Text User Interface) framework for Python inspired by modern web development. Currently a **Work in Progress**. + + +> ⚠ **NOTE:** We ([Textualize.io](https://www.textualize.io)) are hard at work on the **css** branch. We will be maintain the 0.1.0 branch for the near future but may not be able to accept API changes. If you would like to contribute code via a PR, please raise a discussion first, to avoid disapointment. -**NOTE:** This project is currently a work in progress, but usable by brave souls who don't mind some API instability between updates. Follow [@willmcgugan](https://twitter.com/willmcgugan) for progress updates, or post in Discussions if you have any requests / suggestions. From 42585941519716bc742455e8f4113db5e713856e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Jan 2022 13:58:37 +0000 Subject: [PATCH 14/15] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8feba707c..3416fd003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [1.1.15] - Unreleased +## [1.1.15] - 2022-01-31 ### Added From cfefb36ee4dcd4ac3b5c5bde5c040ebeb55f11e6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 31 Jan 2022 14:00:54 +0000 Subject: [PATCH 15/15] Update for windows --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3dadeb019..866cba6f9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Follow [@willmcgugan](https://twitter.com/willmcgugan) for progress updates, or ## Compatibility -Textual currently runs on **MacOS / Linux only**. Windows support is in the pipeline. +Textual currently runs on **MacOS / Linux / Window**. ## How it works