Merge branch 'css' of github.com:Textualize/textual into text-input

This commit is contained in:
Darren Burns
2022-05-17 11:07:43 +01:00
17 changed files with 356 additions and 282 deletions

269
poetry.lock generated
View File

@@ -166,7 +166,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "6.3.2" version = "6.3.3"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev" category = "dev"
optional = false optional = false
@@ -185,7 +185,7 @@ python-versions = "*"
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.6.0" version = "3.7.0"
description = "A platform independent file lock." description = "A platform independent file lock."
category = "dev" category = "dev"
optional = false optional = false
@@ -381,6 +381,14 @@ mkdocs-autorefs = ">=0.1"
pymdown-extensions = ">=6.3" pymdown-extensions = ">=6.3"
pytkdocs = ">=0.14.0" pytkdocs = ">=0.14.0"
[[package]]
name = "msgpack"
version = "1.0.3"
description = "MessagePack (de)serializer."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "multidict" name = "multidict"
version = "6.0.2" version = "6.0.2"
@@ -666,7 +674,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "time-machine" name = "time-machine"
version = "2.6.0" version = "2.7.0"
description = "Travel through time in your tests." description = "Travel through time in your tests."
category = "dev" category = "dev"
optional = false optional = false
@@ -728,7 +736,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "2.1.7" version = "2.1.8"
description = "Filesystem events monitoring" description = "Filesystem events monitoring"
category = "dev" category = "dev"
optional = false optional = false
@@ -765,7 +773,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "37541ff4aa6aa74d76b10b8183b7e62e34b6c66c8b8f8eec7aad23e9b5793f94" content-hash = "3579be8d55deb729ef79984823765900552e19284c205b7bedc92897190fb6fd"
[metadata.files] [metadata.files]
aiohttp = [ aiohttp = [
@@ -916,55 +924,55 @@ commonmark = [
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
] ]
coverage = [ coverage = [
{file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, {file = "coverage-6.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df32ee0f4935a101e4b9a5f07b617d884a531ed5666671ff6ac66d2e8e8246d8"},
{file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, {file = "coverage-6.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75b5dbffc334e0beb4f6c503fb95e6d422770fd2d1b40a64898ea26d6c02742d"},
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, {file = "coverage-6.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114944e6061b68a801c5da5427b9173a0dd9d32cd5fcc18a13de90352843737d"},
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, {file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab88a01cd180b5640ccc9c47232e31924d5f9967ab7edd7e5c91c68eee47a69"},
{file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, {file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad8f9068f5972a46d50fe5f32c09d6ee11da69c560fcb1b4c3baea246ca4109b"},
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4cd696aa712e6cd16898d63cf66139dc70d998f8121ab558f0e1936396dbc579"},
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c1a9942e282cc9d3ed522cd3e3cab081149b27ea3bda72d6f61f84eaf88c1a63"},
{file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c06455121a089252b5943ea682187a4e0a5cf0a3fb980eb8e7ce394b144430a9"},
{file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, {file = "coverage-6.3.3-cp310-cp310-win32.whl", hash = "sha256:cb5311d6ccbd22578c80028c5e292a7ab9adb91bd62c1982087fad75abe2e63d"},
{file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, {file = "coverage-6.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:6d4a6f30f611e657495cc81a07ff7aa8cd949144e7667c5d3e680d73ba7a70e4"},
{file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, {file = "coverage-6.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79bf405432428e989cad7b8bc60581963238f7645ae8a404f5dce90236cc0293"},
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:338c417613f15596af9eb7a39353b60abec9d8ce1080aedba5ecee6a5d85f8d3"},
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db094a6a4ae6329ed322a8973f83630b12715654c197dd392410400a5bfa1a73"},
{file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1414e8b124611bf4df8d77215bd32cba6e3425da8ce9c1f1046149615e3a9a31"},
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:93b16b08f94c92cab88073ffd185070cdcb29f1b98df8b28e6649145b7f2c90d"},
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbc86ae8cc129c801e7baaafe3addf3c8d49c9c1597c44bdf2d78139707c3c62"},
{file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b5ba058610e8289a07db2a57bce45a1793ec0d3d11db28c047aae2aa1a832572"},
{file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, {file = "coverage-6.3.3-cp37-cp37m-win32.whl", hash = "sha256:8329635c0781927a2c6ae068461e19674c564e05b86736ab8eb29c420ee7dc20"},
{file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, {file = "coverage-6.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:e5af1feee71099ae2e3b086ec04f57f9950e1be9ecf6c420696fea7977b84738"},
{file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, {file = "coverage-6.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e814a4a5a1d95223b08cdb0f4f57029e8eab22ffdbae2f97107aeef28554517e"},
{file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, {file = "coverage-6.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f4fbf3633cb0713437291b8848634ea97f89c7e849c2be17a665611e433f53"},
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, {file = "coverage-6.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3401b0d2ed9f726fadbfa35102e00d1b3547b73772a1de5508ef3bdbcb36afe7"},
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, {file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8586b177b4407f988731eb7f41967415b2197f35e2a6ee1a9b9b561f6323c8e9"},
{file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, {file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892e7fe32191960da559a14536768a62e83e87bbb867e1b9c643e7e0fbce2579"},
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afb03f981fadb5aed1ac6e3dd34f0488e1a0875623d557b6fad09b97a942b38a"},
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cbe91bc84be4e5ef0b1480d15c7b18e29c73bdfa33e07d3725da7d18e1b0aff2"},
{file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:91502bf27cbd5c83c95cfea291ef387469f2387508645602e1ca0fd8a4ba7548"},
{file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, {file = "coverage-6.3.3-cp38-cp38-win32.whl", hash = "sha256:c488db059848702aff30aa1d90ef87928d4e72e4f00717343800546fdbff0a94"},
{file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, {file = "coverage-6.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6534fcdfb5c503affb6b1130db7b5bfc8a0f77fa34880146f7a5c117987d0"},
{file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, {file = "coverage-6.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc692c9ee18f0dd3214843779ba6b275ee4bb9b9a5745ba64265bce911aefd1a"},
{file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, {file = "coverage-6.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:462105283de203df8de58a68c1bb4ba2a8a164097c2379f664fa81d6baf94b81"},
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, {file = "coverage-6.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc972d829ad5ef4d4c5fcabd2bbe2add84ce8236f64ba1c0c72185da3a273130"},
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, {file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06f54765cdbce99901871d50fe9f41d58213f18e98b170a30ca34f47de7dd5e8"},
{file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, {file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7835f76a081787f0ca62a53504361b3869840a1620049b56d803a8cb3a9eeea3"},
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f5fee77ec3384b934797f1873758f796dfb4f167e1296dc00f8b2e023ce6ee9"},
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:baa8be8aba3dd1e976e68677be68a960a633a6d44c325757aefaa4d66175050f"},
{file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d06380e777dd6b35ee936f333d55b53dc4a8271036ff884c909cf6e94be8b6c"},
{file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, {file = "coverage-6.3.3-cp39-cp39-win32.whl", hash = "sha256:f8cabc5fd0091976ab7b020f5708335033e422de25e20ddf9416bdce2b7e07d8"},
{file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, {file = "coverage-6.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c9441d57b0963cf8340268ad62fc83de61f1613034b79c2b1053046af0c5284"},
{file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, {file = "coverage-6.3.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:d522f1dc49127eab0bfbba4e90fa068ecff0899bbf61bf4065c790ddd6c177fe"},
{file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, {file = "coverage-6.3.3.tar.gz", hash = "sha256:2781c43bffbbec2b8867376d4d61916f5e9c4cc168232528562a61d1b4b01879"},
] ]
distlib = [ distlib = [
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
{file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
] ]
filelock = [ filelock = [
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, {file = "filelock-3.7.0-py3-none-any.whl", hash = "sha256:c7b5fdb219b398a5b28c8e4c1893ef5f98ece6a38c6ab2c22e26ec161556fed6"},
{file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, {file = "filelock-3.7.0.tar.gz", hash = "sha256:b795f1b42a61bbf8ec7113c341dad679d772567b936fbd1bf43c9a238e673e20"},
] ]
frozenlist = [ frozenlist = [
{file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"},
@@ -1121,6 +1129,42 @@ mkdocstrings = [
{file = "mkdocstrings-0.17.0-py3-none-any.whl", hash = "sha256:103fc1dd58cb23b7e0a6da5292435f01b29dc6fa0ba829132537f3f556f985de"}, {file = "mkdocstrings-0.17.0-py3-none-any.whl", hash = "sha256:103fc1dd58cb23b7e0a6da5292435f01b29dc6fa0ba829132537f3f556f985de"},
{file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"}, {file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"},
] ]
msgpack = [
{file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079"},
{file = "msgpack-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3"},
{file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73"},
{file = "msgpack-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147"},
{file = "msgpack-1.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39"},
{file = "msgpack-1.0.3-cp310-cp310-win32.whl", hash = "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85"},
{file = "msgpack-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7"},
{file = "msgpack-1.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d"},
{file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b"},
{file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec"},
{file = "msgpack-1.0.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770"},
{file = "msgpack-1.0.3-cp36-cp36m-win32.whl", hash = "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9"},
{file = "msgpack-1.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a"},
{file = "msgpack-1.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a"},
{file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc"},
{file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a"},
{file = "msgpack-1.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920"},
{file = "msgpack-1.0.3-cp37-cp37m-win32.whl", hash = "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50"},
{file = "msgpack-1.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba"},
{file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea"},
{file = "msgpack-1.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a"},
{file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef"},
{file = "msgpack-1.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611"},
{file = "msgpack-1.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1"},
{file = "msgpack-1.0.3-cp38-cp38-win32.whl", hash = "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4"},
{file = "msgpack-1.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a"},
{file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3"},
{file = "msgpack-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52"},
{file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2"},
{file = "msgpack-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996"},
{file = "msgpack-1.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2"},
{file = "msgpack-1.0.3-cp39-cp39-win32.whl", hash = "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88"},
{file = "msgpack-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d"},
{file = "msgpack-1.0.3.tar.gz", hash = "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e"},
]
multidict = [ multidict = [
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
@@ -1324,46 +1368,46 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
] ]
time-machine = [ time-machine = [
{file = "time-machine-2.6.0.tar.gz", hash = "sha256:676d0e462f504eb1ef9903cbb92fe4d285af2080fbcbeece9e4b78db5be08687"}, {file = "time-machine-2.7.0.tar.gz", hash = "sha256:0aa0ccd531d7d98e71f7945b65d26d92d31ab74a21a111b9afe61b981c1eb7b2"},
{file = "time_machine-2.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:53b557a82640dda052c36d45573df782f48257f55bd561397b622b1717621702"}, {file = "time_machine-2.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aac9e9fa665d0ed7d105ddaaebda9ef3a2de30aaaf56cfe894f15ba60de8ae09"},
{file = "time_machine-2.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:995e3af3c886c9209fd9fa629d9eb9eaa45b84a090c163206eaf1f3d690fff62"}, {file = "time_machine-2.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:93c6baea546a8edffa57dc691cbb61e5070d6ec791ff7b4a025d4f34b9808516"},
{file = "time_machine-2.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa84cd54a9533a45c5e29cf9d353e54fcca02706718be009564b749a65095c4e"}, {file = "time_machine-2.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f056cc4b9212424eb0796389cb019fb61ed0691674649f824585973f001ab66"},
{file = "time_machine-2.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e81ea179b51abf7a9185cd8b77a416a910c1b4e95b8c1c1cc843247bfd797668"}, {file = "time_machine-2.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dacd42e9f4a81a7c9e308874802e801aab1fed119698958fc00920038a652145"},
{file = "time_machine-2.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22461251ae1e2c1265d56e3df66ff7d3bffe9f1e077dd766a2b7fde9e0dcb609"}, {file = "time_machine-2.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d607303fe6c046b246f1befa82218624db7e92695950c4f6e11f416b8f61129"},
{file = "time_machine-2.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9cec8e59f09b91e4a2321bd1a19ef08bdff44cdc749e5301d3b5e9952a546972"}, {file = "time_machine-2.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:adf738e2d21b265cab4dcb2dc3c0592087f44614a03657c87a6e79131e3be715"},
{file = "time_machine-2.6.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d316b2cc4cc52df913640af3c9b5227b9e5c53b63016487448829bb5e42ddbdb"}, {file = "time_machine-2.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e0a774c3d94b6b4dd58a939ae886d4533ca9f308f71ab6e174d41aa092fcd807"},
{file = "time_machine-2.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a53c4f2cbca166a8a69774101c3b2967ecc12b55d5d9df182de9e42c84dc2781"}, {file = "time_machine-2.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b1115628d39ebe7af89bb187634f119b58f163ee38d9a8369d779a496ac6105"},
{file = "time_machine-2.6.0-cp310-cp310-win32.whl", hash = "sha256:884ba4ea8a2600ed97363e260d083750c4601e61e8011ab17137f662a522d3ae"}, {file = "time_machine-2.7.0-cp310-cp310-win32.whl", hash = "sha256:6ad4b6dfec23acc7b5549fcbe9d632347bd62781471d79e44001242821635393"},
{file = "time_machine-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:345377579d9239d44f53031f3b86115f47639a3e63bcb304d785c141311c166f"}, {file = "time_machine-2.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef7cf446a19536a6c4fcdc74c6023bf4c85f6ebe97c63b18b4bf97905bf7919d"},
{file = "time_machine-2.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab14bb97dca07b8e882677feed4ffe2e7019d604dfca36e916a67308a3e0031d"}, {file = "time_machine-2.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:252f53e72da83e876c004e312d15a2e7c920b84dc60a469ad3c5404c5aa0d2b1"},
{file = "time_machine-2.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a93e6973b778c586db54ccf666c9af1f9648fa020dbdd815fc0f184a3a3ef24"}, {file = "time_machine-2.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f39695eb780795f7109ddf6b6c04d97f5610d75b08eb968deed3f0e09c43bb0"},
{file = "time_machine-2.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d785189082d9add90ddd9bfeab1f257a3b28a3f302f2ff29e673bd0f226787"}, {file = "time_machine-2.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c6236ea7f2744f8b9ceab85c9cd71f0aeb03530e2b47377678c39fcd77aafb6"},
{file = "time_machine-2.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91beb7ad1da8986776a71f14e8f38397eaea1dbc12aaf1907e68a597e2e71aaa"}, {file = "time_machine-2.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39aabce90c4c936abe78efe483a6b2fe99901ff431afbeab65c0815eeff66f0b"},
{file = "time_machine-2.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8366b48826005b30a6cd7dc7d8ce6f801fd240eadebe9495becab3fea9b6a7d7"}, {file = "time_machine-2.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:75cdf9aa1b0fba833ca4ebafd738a88983a6597caa4b6ac4a9bfa83c6ca7d8fc"},
{file = "time_machine-2.6.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9e69336008f7fb446d315006971a2beb52504465ef76430c5386ed0d0d28d5a4"}, {file = "time_machine-2.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:038e642d88ba5b76bfa77ec95bcf0d35d7fc6c6ea264fc0aa36e02eff09d3ad2"},
{file = "time_machine-2.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:90d3c126e407991da64555ab3f36d2674f77ae328e514567b7a4471011f758b9"}, {file = "time_machine-2.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3cca3f11059ab6d39dc33495be94ecceaa3d6760b7a983a5c67e172d6b5f6781"},
{file = "time_machine-2.6.0-cp37-cp37m-win32.whl", hash = "sha256:e85b954529599291b24a6562f2a9f1131a1fddfaef1b51b588096dc4786c860e"}, {file = "time_machine-2.7.0-cp37-cp37m-win32.whl", hash = "sha256:1d40c3be8b075868e73e09a9f600ecf383ad30c8921806c7dc0915820b8fa1c7"},
{file = "time_machine-2.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3588a83acf930c71112b671b1e91d52f7e5f9e427cb55b4c0d27b2aeab219244"}, {file = "time_machine-2.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e125d7a406f8a3b48ddd3f836cf41be98de446db76147a2da8d1f1e82254946e"},
{file = "time_machine-2.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a99e97227cd13baf0add2ac306392906490db219df07c7399fee54d000da486e"}, {file = "time_machine-2.7.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7c35a6971ebfe10ed4141a5e5da98cbb7b0f029385d7d97f47b19772e34cdd8a"},
{file = "time_machine-2.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ec280e2a7225533ec623d422ad8dbe4017473fc39b0a68411969a157299ecb4"}, {file = "time_machine-2.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2c3a7f36e3c12f229c651a9e58e5a034d2360c27bea24bfebf95001ce1359b4"},
{file = "time_machine-2.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:857ad51f7361e7c9239a90147339f771fdfba09fb9b60357f70e94e7ff75f3d6"}, {file = "time_machine-2.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16ba783c4836a7dc74221a6b937deb88805ccb208a246a160d716c426c074462"},
{file = "time_machine-2.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:876f58eb6edf587d6faf91440c35016bfefa753c7412e65cc1138ee0df1afc31"}, {file = "time_machine-2.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7ba3a382b96db3f47c9f93207aefdc2817563e3ec727dbda38399035bcd475"},
{file = "time_machine-2.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20503e31c76cd1f731050e815788f9671e79f0db753ec5f2ac42962f36b7ad2"}, {file = "time_machine-2.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:514413eb7f0b55bd36a5e2ab67b357430c8038382ddb5896ff67f73668b1a23f"},
{file = "time_machine-2.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5117e1a0bafbe8a7c975f8cd3f41f8148f996951fddb07c2bc7331afbbd6f329"}, {file = "time_machine-2.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62a8ae6dbb9ed623f7e42089661974a8fa256c4c64c5391d1f6c60da2b8d54c8"},
{file = "time_machine-2.6.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:52eed92cac2e6f303d33f65b21a48803c0c173ef93bd87192aa06f92f3e63d4e"}, {file = "time_machine-2.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fcebeff41e50445100f3f881699d8deeda8f1c6dd80c0c0381b61977aae9dedc"},
{file = "time_machine-2.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8230b7a8c3f664cbe8e25101b684cb8dc2c5c7f97bf7d4b99fa4d193d685511"}, {file = "time_machine-2.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:985e773fa70a03b41ef33e1e77b0a655ca1503b77f60d52b613fe1707636b93d"},
{file = "time_machine-2.6.0-cp38-cp38-win32.whl", hash = "sha256:31e2c58378e678054231b56544b42f51438b0423755e3345997afead6145db5f"}, {file = "time_machine-2.7.0-cp38-cp38-win32.whl", hash = "sha256:0579fa83e608ef4f1b16bd63e0a01c2bec5c4b3f8c2349a158af2c886cc8e0b5"},
{file = "time_machine-2.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c4f80e85ed52b4e22c8b966f50b82063b571bbd2ed2906c084d249337a1b1a0"}, {file = "time_machine-2.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc735fdfa2cbe00f63ed66c48cf32c13b91b9dd9817bec37bdb9c2df5ea09da8"},
{file = "time_machine-2.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9c0b607c7b2b0c9be0a56f0ab41a7e1c5e0c251faee4d7a7d7a816fc4fa43ba5"}, {file = "time_machine-2.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1ae378d4bd4101edd605e82da4859e0a5c509ddf2df0f8094160d795fc22777"},
{file = "time_machine-2.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d7fa4d2a70ab088b967e273468b1e9e0020aa529843ed907b2a889c92e09766"}, {file = "time_machine-2.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:747825e968290c8ec98202a60411586edd9b8669cbbf31f61224240e5951d1fb"},
{file = "time_machine-2.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182b5a30c6b6e2d761d181d42b71469b237e1dd06267428a008e032e5e33d4e3"}, {file = "time_machine-2.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd4797046126bfa1524a9ef8acac83282ce9365ad2cbcab0843ee2662e103502"},
{file = "time_machine-2.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ef26ee01527d0ac44e73d27b5d58c04322d9bf0e46874545ff27c46adebf026"}, {file = "time_machine-2.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50585410381f9b9799fdd0ab7b1ac5009a341127cf9e72222bc2cb870eddf44d"},
{file = "time_machine-2.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c088359f5d571ba6ab3c99fce5d55d433f96117c33b8f75c49a0ef610e6ae41e"}, {file = "time_machine-2.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd0b8cf6ad69ed54d2e11c2d5cc3681c6defc7ce022121312cead610d54685c3"},
{file = "time_machine-2.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20fb6e83d30a37294cb274124f99d97a1990d8919a9ac8517d8bec1ef370b1e8"}, {file = "time_machine-2.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:72d03615caf11eb47c7efeb48c839e307a18c272637d967b697a02f6561dad3c"},
{file = "time_machine-2.6.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fb4d1146bac245908ad8d62e16e92c0e1d4b194499d7f6301107e837a7fd92aa"}, {file = "time_machine-2.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa5a0a01f9562be8a13338255a9497b3c1fc8bb018582fa4a881184a9f480c9a"},
{file = "time_machine-2.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b81076bbf81fa41279b232e5c0a50194345801b26fd0b4610ec040c5626a5ec1"}, {file = "time_machine-2.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:06473db7145f82992d47f02d04a164abe499ec3f4ab24fc9877d49a7e46ae7a0"},
{file = "time_machine-2.6.0-cp39-cp39-win32.whl", hash = "sha256:7200f094198b7d382942caf6cc89d45464acf19b7534fde081f10f532021f39e"}, {file = "time_machine-2.7.0-cp39-cp39-win32.whl", hash = "sha256:b377991a5ead8f4d4c887293fde66cb1e8abb5c709e311c724016d16e2ef4c7d"},
{file = "time_machine-2.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:cc2d77ba51588ba3e1e539dbfd2459092159c577bf777b5c976e5cfbbf84d554"}, {file = "time_machine-2.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:5ba2f608684b19be35b6bc0dca362eca2ded0b7c6f1ff88ed1e55b8dfdfb869d"},
] ]
toml = [ toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
@@ -1408,30 +1452,31 @@ virtualenv = [
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
] ]
watchdog = [ watchdog = [
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383"}, {file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:676263bee67b165f16b05abc52acc7a94feac5b5ab2449b491f1a97638a79277"},
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4"}, {file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aa68d2d9a89d686fae99d28a6edf3b18595e78f5adf4f5c18fbfda549ac0f20c"},
{file = "watchdog-2.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca"}, {file = "watchdog-2.1.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e2e51c53666850c3ecffe9d265fc5d7351db644de17b15e9c685dd3cdcd6f97"},
{file = "watchdog-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa"}, {file = "watchdog-2.1.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7721ac736170b191c50806f43357407138c6748e4eb3e69b071397f7f7aaeedd"},
{file = "watchdog-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab"}, {file = "watchdog-2.1.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ce7376aed3da5fd777483fe5ebc8475a440c6d18f23998024f832134b2938e7b"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a"}, {file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f9ee4c6bf3a1b2ed6be90a2d78f3f4bbd8105b6390c04a86eb48ed67bbfa0b0b"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8"}, {file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:68dbe75e0fa1ba4d73ab3f8e67b21770fbed0651d32ce515cd38919a26873266"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2"}, {file = "watchdog-2.1.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c520009b8cce79099237d810aaa19bc920941c268578436b62013b2f0102320"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99"}, {file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efcc8cbc1b43902571b3dce7ef53003f5b97fe4f275fe0489565fc6e2ebe3314"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b"}, {file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:746e4c197ec1083581bb1f64d07d1136accf03437badb5ff8fcb862565c193b2"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd"}, {file = "watchdog-2.1.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ae17b6be788fb8e4d8753d8d599de948f0275a232416e16436363c682c6f850"},
{file = "watchdog-2.1.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420"}, {file = "watchdog-2.1.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ddde157dc1447d8130cb5b8df102fad845916fe4335e3d3c3f44c16565becbb7"},
{file = "watchdog-2.1.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c"}, {file = "watchdog-2.1.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4978db33fc0934c92013ee163a9db158ec216099b69fce5aec790aba704da412"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d"}, {file = "watchdog-2.1.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b962de4d7d92ff78fb2dbc6a0cb292a679dea879a0eb5568911484d56545b153"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_armv7l.whl", hash = "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1e5d0fdfaa265c29dc12621913a76ae99656cf7587d03950dfeb3595e5a26102"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_i686.whl", hash = "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_armv7l.whl", hash = "sha256:036ed15f7cd656351bf4e17244447be0a09a61aaa92014332d50719fc5973bc0"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64.whl", hash = "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_i686.whl", hash = "sha256:2962628a8777650703e8f6f2593065884c602df7bae95759b2df267bd89b2ef5"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64.whl", hash = "sha256:156ec3a94695ea68cfb83454b98754af6e276031ba1ae7ae724dc6bf8973b92a"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_s390x.whl", hash = "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:47598fe6713fc1fee86b1ca85c9cbe77e9b72d002d6adeab9c3b608f8a5ead10"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_s390x.whl", hash = "sha256:fed4de6e45a4f16e4046ea00917b4fe1700b97244e5d114f594b4a1b9de6bed8"},
{file = "watchdog-2.1.7-py3-none-win32.whl", hash = "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6"}, {file = "watchdog-2.1.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:24dedcc3ce75e150f2a1d704661f6879764461a481ba15a57dc80543de46021c"},
{file = "watchdog-2.1.7-py3-none-win_amd64.whl", hash = "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566"}, {file = "watchdog-2.1.8-py3-none-win32.whl", hash = "sha256:6ddf67bc9f413791072e3afb466e46cc72c6799ba73dea18439b412e8f2e3257"},
{file = "watchdog-2.1.7-py3-none-win_ia64.whl", hash = "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572"}, {file = "watchdog-2.1.8-py3-none-win_amd64.whl", hash = "sha256:88ef3e8640ef0a64b7ad7394b0f23384f58ac19dd759da7eaa9bc04b2898943f"},
{file = "watchdog-2.1.7.tar.gz", hash = "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480"}, {file = "watchdog-2.1.8-py3-none-win_ia64.whl", hash = "sha256:0fb60c7d31474b21acba54079ce9ff0136411183e9a591369417cddb1d7d00d7"},
{file = "watchdog-2.1.8.tar.gz", hash = "sha256:6d03149126864abd32715d4e9267d2754cede25a69052901399356ad3bc5ecff"},
] ]
yarl = [ yarl = [
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},

View File

@@ -28,6 +28,7 @@ rich = "^12.4.0"
click = "8.1.2" click = "8.1.2"
importlib-metadata = "^4.11.3" importlib-metadata = "^4.11.3"
typing-extensions = { version = "^4.0.0", python = "<3.8" } typing-extensions = { version = "^4.0.0", python = "<3.8" }
msgpack = "^1.0.3"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^6.2.3" pytest = "^6.2.3"

View File

@@ -6,7 +6,6 @@
transition: color 300ms linear, background 300ms linear; transition: color 300ms linear, background 300ms linear;
} }
* { * {
scrollbar-background: $panel-darken-2; scrollbar-background: $panel-darken-2;
scrollbar-background-hover: $panel-darken-3; scrollbar-background-hover: $panel-darken-3;

View File

@@ -246,8 +246,3 @@ class Animator:
animation = self._animations[animation_key] animation = self._animations[animation_key]
if animation(animation_time): if animation(animation_time):
del self._animations[animation_key] del self._animations[animation_key]
self.on_animation_frame()
def on_animation_frame(self) -> None:
# TODO: We should be able to do animation without refreshing everything
self.target.screen.refresh_layout()

View File

@@ -26,7 +26,6 @@ from rich.style import Style
from . import errors from . import errors
from .geometry import Region, Offset, Size from .geometry import Region, Offset, Size
from ._loop import loop_last from ._loop import loop_last
from ._segment_tools import line_crop from ._segment_tools import line_crop
from ._types import Lines from ._types import Lines
@@ -38,7 +37,6 @@ else: # pragma: no cover
if TYPE_CHECKING: if TYPE_CHECKING:
from .screen import Screen
from .widget import Widget from .widget import Widget
@@ -59,6 +57,11 @@ class MapGeometry(NamedTuple):
virtual_size: Size # The virtual size (scrollable region) of a widget if it is a container virtual_size: Size # The virtual size (scrollable region) of a widget if it is a container
container_size: Size # The container size (area not occupied by scrollbars) container_size: Size # The container size (area not occupied by scrollbars)
@property
def visible_region(self) -> Region:
"""The Widget region after clipping."""
return self.clip.intersection(self.region)
CompositorMap: TypeAlias = "dict[Widget, MapGeometry]" CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"
@@ -78,7 +81,6 @@ class LayoutUpdate:
new_line = Segment.line() new_line = Segment.line()
move_to = Control.move_to move_to = Control.move_to
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)): for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
yield Control.home()
yield move_to(x, y) yield move_to(x, y)
yield from line yield from line
if not last: if not last:
@@ -144,6 +146,17 @@ class Compositor:
# The points in each line where the line bisects the left and right edges of the widget # The points in each line where the line bisects the left and right edges of the widget
self._cuts: list[list[int]] | None = None self._cuts: list[list[int]] | None = None
# Regions that require an update
self._dirty_regions: set[Region] = set()
def add_dirty_regions(self, regions: Iterable[Region]) -> None:
"""Add dirty regions to be repainted next call to render.
Args:
regions (Iterable[Region]): Regions that are "dirty" (changed since last render).
"""
self._dirty_regions.update(regions)
@classmethod @classmethod
def _regions_to_spans( def _regions_to_spans(
cls, regions: Iterable[Region] cls, regions: Iterable[Region]
@@ -198,11 +211,12 @@ class Compositor:
self.root = parent self.root = parent
self.size = size self.size = size
# TODO: Handle virtual size # Keep a copy of the old map because we're going to compare it with the update
old_map = self.map.copy()
old_widgets = old_map.keys()
map, widgets = self._arrange_root(parent) map, widgets = self._arrange_root(parent)
new_widgets = map.keys()
old_widgets = set(self.map.keys())
new_widgets = set(map.keys())
# Newly visible widgets # Newly visible widgets
shown_widgets = new_widgets - old_widgets shown_widgets = new_widgets - old_widgets
# Newly hidden widgets # Newly hidden widgets
@@ -212,7 +226,7 @@ class Compositor:
self.map = map self.map = map
self.widgets = widgets self.widgets = widgets
# Copy renders if the size hasn't changed # Get a map of regions
self.regions = { self.regions = {
widget: (region, clip) widget: (region, clip)
for widget, (region, _order, clip, _, _) in map.items() for widget, (region, _order, clip, _, _) in map.items()
@@ -225,6 +239,21 @@ class Compositor:
if widget in old_widgets and widget.size != region.size if widget in old_widgets and widget.size != region.size
} }
# Gets pairs of tuples of (Widget, MapGeometry) which have changed
# i.e. if something is moved / deleted / added
screen = size.region
if screen not in self._dirty_regions:
crop_screen = screen.intersection
changes: set[tuple[Widget, MapGeometry]] = (
self.map.items() ^ old_map.items()
)
self._dirty_regions.update(
[
crop_screen(map_geometry.visible_region)
for _, map_geometry in changes
]
)
return ReflowResult( return ReflowResult(
hidden=hidden_widgets, hidden=hidden_widgets,
shown=shown_widgets, shown=shown_widgets,
@@ -516,29 +545,32 @@ class Compositor:
] ]
return segment_lines return segment_lines
def render(self, regions: list[Region] | None = None) -> RenderableType: def render(self) -> RenderableType:
"""Render a layout. """Render a layout.
Args:
clip (Optional[Region]): Region to clip to.
Returns: Returns:
SegmentLines: A renderable SegmentLines: A renderable
""" """
width, height = self.size width, height = self.size
screen_region = Region(0, 0, width, height) screen_region = Region(0, 0, width, height)
if regions:
update_regions = self._dirty_regions.copy()
if screen_region in update_regions:
# If one of the updates is the entire screen, then we only need one update
update_regions.clear()
self._dirty_regions.clear()
if update_regions:
# Create a crop regions that surrounds all updates # Create a crop regions that surrounds all updates
crop = Region.from_union(regions).intersection(screen_region) crop = Region.from_union(list(update_regions)).intersection(screen_region)
spans = list(self._regions_to_spans(regions)) spans = list(self._regions_to_spans(update_regions))
is_rendered_line = {y for y, _, _ in spans}.__contains__ is_rendered_line = {y for y, _, _ in spans}.__contains__
else: else:
crop = screen_region crop = screen_region
spans = [] spans = []
is_rendered_line = lambda y: True is_rendered_line = lambda y: True
_Segment = Segment divide = Segment.divide
divide = _Segment.divide
# Maps each cut on to a list of segments # Maps each cut on to a list of segments
cuts = self.cuts cuts = self.cuts
@@ -569,7 +601,6 @@ class Compositor:
else: else:
render_x = render_region.x render_x = render_region.x
relative_cuts = [cut - render_x for cut in final_cuts] relative_cuts = [cut - render_x for cut in final_cuts]
# print(relative_cuts)
_, *cut_segments = divide(line, relative_cuts) _, *cut_segments = divide(line, relative_cuts)
# Since we are painting front to back, the first segments for a cut "wins" # Since we are painting front to back, the first segments for a cut "wins"
@@ -578,7 +609,7 @@ class Compositor:
if chops_line[cut] is None: if chops_line[cut] is None:
chops_line[cut] = segments chops_line[cut] = segments
if regions: if update_regions:
crop_y, crop_y2 = crop.y_extents crop_y, crop_y2 = crop.y_extents
render_lines = self._assemble_chops(chops[crop_y:crop_y2]) render_lines = self._assemble_chops(chops[crop_y:crop_y2])
render_spans = [ render_spans = [
@@ -596,15 +627,13 @@ class Compositor:
) -> RenderResult: ) -> RenderResult:
yield self.render() yield self.render()
def update_widgets(self, widgets: set[Widget]) -> RenderableType | None: def update_widgets(self, widgets: set[Widget]) -> None:
"""Update a given widget in the composition. """Update a given widget in the composition.
Args: Args:
console (Console): Console instance. console (Console): Console instance.
widget (Widget): Widget to update. widget (Widget): Widget to update.
Returns:
LayoutUpdate | None: A renderable or None if nothing to render.
""" """
regions: list[Region] = [] regions: list[Region] = []
add_region = regions.append add_region = regions.append
@@ -613,5 +642,4 @@ class Compositor:
update_region = region.intersection(clip) update_region = region.intersection(clip)
if update_region: if update_region:
add_region(update_region) add_region(update_region)
update = self.render(regions or None) self.add_dirty_regions(regions)
return update

View File

@@ -1,7 +1,9 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from datetime import datetime
import inspect import inspect
import io
import os import os
import platform import platform
import sys import sys
@@ -28,10 +30,8 @@ else:
import rich import rich
import rich.repr import rich.repr
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.control import Control
from rich.measure import Measurement from rich.measure import Measurement
from rich.protocol import is_renderable from rich.protocol import is_renderable
from rich.screen import Screen as ScreenRenderable
from rich.segment import Segments from rich.segment import Segments
from rich.style import Style from rich.style import Style
from rich.traceback import Traceback from rich.traceback import Traceback
@@ -192,7 +192,6 @@ class App(Generic[ReturnType], DOMNode):
self.registry: set[MessagePump] = set() self.registry: set[MessagePump] = set()
self.devtools = DevtoolsClient() self.devtools = DevtoolsClient()
self._return_value: ReturnType | None = None self._return_value: ReturnType | None = None
self._focus_timer: Timer | None = None
self.css_monitor = ( self.css_monitor = (
FileMonitor(css_path, self._on_css_change) FileMonitor(css_path, self._on_css_change)
@@ -263,10 +262,6 @@ class App(Generic[ReturnType], DOMNode):
Returns: Returns:
Widget | None: Newly focused widget, or None for no focus. Widget | None: Newly focused widget, or None for no focus.
""" """
if self._focus_timer:
# Cancel the timer that clears the show focus class
# We will be creating a new timer to extend the time until the focus is hidden
self._focus_timer.stop_no_wait()
focusable_widgets = self.focus_chain focusable_widgets = self.focus_chain
if not focusable_widgets: if not focusable_widgets:
@@ -284,12 +279,10 @@ class App(Generic[ReturnType], DOMNode):
self.set_focus(focusable_widgets[0]) self.set_focus(focusable_widgets[0])
else: else:
# Only move the focus if we are currently showing the focus # Only move the focus if we are currently showing the focus
if direction and self.has_class("-show-focus"): if direction:
current_index = (current_index + direction) % len(focusable_widgets) current_index = (current_index + direction) % len(focusable_widgets)
self.set_focus(focusable_widgets[current_index]) self.set_focus(focusable_widgets[current_index])
self._focus_timer = self.set_timer(2, self.hide_focus)
self.add_class("-show-focus")
return self.focused return self.focused
def show_focus(self) -> Widget | None: def show_focus(self) -> Widget | None:
@@ -316,15 +309,6 @@ class App(Generic[ReturnType], DOMNode):
""" """
return self._move_focus(-1) return self._move_focus(-1)
def hide_focus(self) -> None:
"""Hide the focus.
Returns:
Widget | None: Newly focused widget, or None for no focus.
"""
self.remove_class("-show-focus")
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Yield child widgets for a container.""" """Yield child widgets for a container."""
return return
@@ -427,6 +411,49 @@ class App(Generic[ReturnType], DOMNode):
except Exception: except Exception:
pass pass
def action_screenshot(self, path: str | None = None) -> None:
"""Action to save a screenshot."""
self.save_screenshot(path)
def export_screenshot(self) -> str:
"""Export a SVG screenshot of the current screen.
Args:
path (str | None, optional): Path of the SVG to save, or None to
generate a path automatically. Defaults to None.
"""
console = Console(
width=self.console.width,
height=self.console.height,
file=io.StringIO(),
force_terminal=True,
color_system="truecolor",
record=True,
)
console.print(self.screen._compositor)
return console.export_svg(title=self.title)
def save_screenshot(self, path: str | None = None) -> str:
"""Save a screenshot of the current screen.
Args:
path (str | None, optional): Path to SVG to save or None to pick
a filename automatically. Defaults to None.
Returns:
str: Filename of screenshot.
"""
if path is None:
svg_path = f"{self.title.lower()}_{datetime.now().isoformat()}.svg"
svg_path = svg_path.replace("/", "_").replace("\\", "_")
else:
svg_path = path
screenshot_svg = self.export_screenshot()
with open(svg_path, "w") as svg_file:
svg_file.write(screenshot_svg)
return svg_path
def bind( def bind(
self, self,
keys: str, keys: str,
@@ -525,7 +552,6 @@ class App(Generic[ReturnType], DOMNode):
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None: def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
self.register(self.screen, *anon_widgets, **widgets) self.register(self.screen, *anon_widgets, **widgets)
self.screen.refresh()
def push_screen(self, screen: Screen | None = None) -> Screen: def push_screen(self, screen: Screen | None = None) -> Screen:
"""Push a new screen on the screen stack. """Push a new screen on the screen stack.
@@ -730,7 +756,6 @@ class App(Generic[ReturnType], DOMNode):
widgets = list(self.compose()) widgets = list(self.compose())
if widgets: if widgets:
self.mount(*widgets) self.mount(*widgets)
self.screen.refresh()
async def on_idle(self) -> None: async def on_idle(self) -> None:
"""Perform actions when there are no messages in the queue.""" """Perform actions when there are no messages in the queue."""
@@ -816,13 +841,7 @@ class App(Generic[ReturnType], DOMNode):
try: try:
if self._sync_available: if self._sync_available:
console.file.write("\x1bP=1s\x1b\\") console.file.write("\x1bP=1s\x1b\\")
console.print( console.print(self.screen._compositor)
ScreenRenderable(
Control.home(),
self.screen._compositor,
Control.home(),
)
)
if self._sync_available: if self._sync_available:
console.file.write("\x1bP=2s\x1b\\") console.file.write("\x1bP=2s\x1b\\")
console.file.flush() console.file.flush()
@@ -846,10 +865,15 @@ class App(Generic[ReturnType], DOMNode):
return return
if not self._closed: if not self._closed:
console = self.console console = self.console
if self._sync_available:
console.file.write("\x1bP=1s\x1b\\")
try: try:
console.print(renderable) console.print(renderable)
except Exception as error: except Exception as error:
self.on_exception(error) self.on_exception(error)
if self._sync_available:
console.file.write("\x1bP=2s\x1b\\")
console.file.flush()
def measure(self, renderable: RenderableType, max_width=100_000) -> int: def measure(self, renderable: RenderableType, max_width=100_000) -> int:
"""Get the optimal width for a widget or renderable. """Get the optimal width for a widget or renderable.

View File

@@ -1,11 +1,12 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import base64
import datetime import datetime
import inspect import inspect
import json import json
import msgpack
import pickle import pickle
from time import time
from asyncio import Queue, Task, QueueFull from asyncio import Queue, Task, QueueFull
from io import StringIO from io import StringIO
from typing import Type, Any, NamedTuple from typing import Type, Any, NamedTuple
@@ -97,7 +98,7 @@ class DevtoolsClient:
self.update_console_task: Task | None = None self.update_console_task: Task | None = None
self.console: DevtoolsConsole = DevtoolsConsole(file=StringIO()) self.console: DevtoolsConsole = DevtoolsConsole(file=StringIO())
self.websocket: ClientWebSocketResponse | None = None self.websocket: ClientWebSocketResponse | None = None
self.log_queue: Queue[str | Type[ClientShutdown]] | None = None self.log_queue: Queue[str | bytes | Type[ClientShutdown]] | None = None
self.spillover: int = 0 self.spillover: int = 0
async def connect(self) -> None: async def connect(self) -> None:
@@ -144,7 +145,10 @@ class DevtoolsClient:
if log is ClientShutdown: if log is ClientShutdown:
log_queue.task_done() log_queue.task_done()
break break
await websocket.send_str(log) if isinstance(log, str):
await websocket.send_str(log)
else:
await websocket.send_bytes(log)
log_queue.task_done() log_queue.task_done()
self.log_queue_task = asyncio.create_task(send_queued_logs()) self.log_queue_task = asyncio.create_task(send_queued_logs())
@@ -203,17 +207,18 @@ class DevtoolsClient:
segments = self.console.export_segments() segments = self.console.export_segments()
encoded_segments = self._encode_segments(segments) encoded_segments = self._encode_segments(segments)
message = json.dumps( message: bytes | None = msgpack.packb(
{ {
"type": "client_log", "type": "client_log",
"payload": { "payload": {
"timestamp": int(datetime.datetime.utcnow().timestamp()), "timestamp": int(time()),
"path": getattr(log.caller, "filename", ""), "path": getattr(log.caller, "filename", ""),
"line_number": getattr(log.caller, "lineno", 0), "line_number": getattr(log.caller, "lineno", 0),
"encoded_segments": encoded_segments, "segments": encoded_segments,
}, },
} }
) )
assert message is not None
try: try:
if self.log_queue: if self.log_queue:
self.log_queue.put_nowait(message) self.log_queue.put_nowait(message)
@@ -233,15 +238,15 @@ class DevtoolsClient:
except QueueFull: except QueueFull:
self.spillover += 1 self.spillover += 1
def _encode_segments(self, segments: list[Segment]) -> str: @classmethod
"""Pickle and Base64 encode the list of Segments def _encode_segments(cls, segments: list[Segment]) -> bytes:
"""Pickle a list of Segments
Args: Args:
segments (list[Segment]): A list of Segments to encode segments (list[Segment]): A list of Segments to encode
Returns: Returns:
str: The Segment list pickled with pickle protocol v3, then base64 encoded bytes: The Segment list pickled with the latest protocol.
""" """
pickled = pickle.dumps(segments, protocol=3) pickled = pickle.dumps(segments, protocol=4)
encoded = base64.b64encode(pickled) return pickled
return str(encoded, encoding="utf-8")

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from datetime import datetime, timezone from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Iterable from typing import Iterable
@@ -72,19 +72,13 @@ class DevConsoleLog:
def __rich_console__( def __rich_console__(
self, console: Console, options: ConsoleOptions self, console: Console, options: ConsoleOptions
) -> RenderResult: ) -> RenderResult:
local_time = ( local_time = datetime.fromtimestamp(self.unix_timestamp)
datetime.fromtimestamp(self.unix_timestamp)
.replace(tzinfo=timezone.utc)
.astimezone(tz=datetime.now().astimezone().tzinfo)
)
timezone_name = local_time.tzname()
table = Table.grid(expand=True) table = Table.grid(expand=True)
table.add_column()
table.add_column()
file_link = escape(f"file://{Path(self.path).absolute()}") file_link = escape(f"file://{Path(self.path).absolute()}")
file_and_line = escape(f"{Path(self.path).name}:{self.line_number}") file_and_line = escape(f"{Path(self.path).name}:{self.line_number}")
table.add_row( table.add_row(
f"[dim]{local_time.time()} {timezone_name}", f"[dim]{local_time.time()}",
Align.right( Align.right(
Text(f"{file_and_line}", style=Style(dim=True, link=file_link)) Text(f"{file_and_line}", style=Style(dim=True, link=file_link))
), ),

View File

@@ -14,6 +14,7 @@ from aiohttp.abc import Request
from aiohttp.web_ws import WebSocketResponse from aiohttp.web_ws import WebSocketResponse
from rich.console import Console from rich.console import Console
from rich.markup import escape from rich.markup import escape
import msgpack
from textual.devtools.renderables import ( from textual.devtools.renderables import (
DevConsoleLog, DevConsoleLog,
@@ -160,26 +161,25 @@ class ClientHandler:
""" """
last_message_time: float | None = None last_message_time: float | None = None
while True: while True:
message_json = await self.incoming_queue.get() message = await self.incoming_queue.get()
if message_json is None: if message is None:
self.incoming_queue.task_done() self.incoming_queue.task_done()
break break
type = message_json["type"] type = message["type"]
if type == "client_log": if type == "client_log":
path = message_json["payload"]["path"] path = message["payload"]["path"]
line_number = message_json["payload"]["line_number"] line_number = message["payload"]["line_number"]
timestamp = message_json["payload"]["timestamp"] timestamp = message["payload"]["timestamp"]
encoded_segments = message_json["payload"]["encoded_segments"] encoded_segments = message["payload"]["segments"]
decoded_segments = base64.b64decode(encoded_segments) segments = pickle.loads(encoded_segments)
segments = pickle.loads(decoded_segments)
message_time = time() message_time = time()
if ( if (
last_message_time is not None last_message_time is not None
and message_time - last_message_time > 1 and message_time - last_message_time > 1
): ):
# Print a rule if it has been longer than a second since the last message # Print a rule if it has been longer than a second since the last message
self.service.console.rule("") self.service.console.rule()
self.service.console.print( self.service.console.print(
DevConsoleLog( DevConsoleLog(
segments=segments, segments=segments,
@@ -190,7 +190,7 @@ class ClientHandler:
) )
last_message_time = message_time last_message_time = message_time
elif type == "client_spillover": elif type == "client_spillover":
spillover = int(message_json["payload"]["spillover"]) spillover = int(message["payload"]["spillover"])
info_renderable = DevConsoleNotice( info_renderable = DevConsoleNotice(
f"Discarded {spillover} messages", level="warning" f"Discarded {spillover} messages", level="warning"
) )
@@ -219,21 +219,26 @@ class ClientHandler:
await self.service.send_server_info(client_handler=self) await self.service.send_server_info(client_handler=self)
async for message in self.websocket: async for message in self.websocket:
message = cast(WSMessage, message) message = cast(WSMessage, message)
if message.type == WSMsgType.TEXT:
if message.type in (WSMsgType.TEXT, WSMsgType.BINARY):
try: try:
message_json = json.loads(message.data) if isinstance(message.data, bytes):
message = msgpack.unpackb(message.data)
else:
message = json.loads(message.data)
except JSONDecodeError: except JSONDecodeError:
self.service.console.print(escape(str(message.data))) self.service.console.print(escape(str(message.data)))
continue continue
type = message_json.get("type") type = message.get("type")
if not type: if not type:
continue continue
if ( if (
type in QUEUEABLE_TYPES type in QUEUEABLE_TYPES
and not self.service.shutdown_event.is_set() and not self.service.shutdown_event.is_set()
): ):
await self.incoming_queue.put(message_json) await self.incoming_queue.put(message)
elif message.type == WSMsgType.ERROR: elif message.type == WSMsgType.ERROR:
self.service.console.print( self.service.console.print(
DevConsoleNotice("Websocket error occurred", level="error") DevConsoleNotice("Websocket error occurred", level="error")

View File

@@ -6,7 +6,7 @@ Functions and classes to manage terminal geometry (anything involving coordinate
from __future__ import annotations from __future__ import annotations
from typing import Any, cast, Iterable, NamedTuple, Tuple, Union, TypeVar from typing import Any, cast, NamedTuple, Sequence, Tuple, Union, TypeVar
SpacingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]] SpacingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
@@ -182,7 +182,7 @@ class Region(NamedTuple):
height: int = 0 height: int = 0
@classmethod @classmethod
def from_union(cls, regions: list[Region]) -> Region: def from_union(cls, regions: Sequence[Region]) -> Region:
"""Create a Region from the union of other regions. """Create a Region from the union of other regions.
Args: Args:

View File

@@ -94,13 +94,10 @@ class Screen(Widget):
def _on_update(self) -> None: def _on_update(self) -> None:
"""Called by the _update_timer.""" """Called by the _update_timer."""
# Render widgets together # Render widgets together
if self._dirty_widgets: if self._dirty_widgets:
self.log(dirty=self._dirty_widgets) self._compositor.update_widgets(self._dirty_widgets)
display_update = self._compositor.update_widgets(self._dirty_widgets) self.app.display(self._compositor.render())
if display_update is not None:
self.app.display(display_update)
self._dirty_widgets.clear() self._dirty_widgets.clear()
self._update_timer.pause() self._update_timer.pause()
@@ -109,8 +106,8 @@ class Screen(Widget):
if not self.size: if not self.size:
return return
# This paint the entire screen, so replaces the batched dirty widgets # This paint the entire screen, so replaces the batched dirty widgets
self._compositor.update_widgets(self._dirty_widgets)
self._update_timer.pause() self._update_timer.pause()
self._dirty_widgets.clear()
try: try:
hidden, shown, resized = self._compositor.reflow(self, self.size) hidden, shown, resized = self._compositor.reflow(self, self.size)
@@ -140,7 +137,10 @@ class Screen(Widget):
except Exception as error: except Exception as error:
self.app.on_exception(error) self.app.on_exception(error)
return return
self.app.refresh()
display_update = self._compositor.render()
if display_update is not None:
self.app.display(display_update)
async def handle_update(self, message: messages.Update) -> None: async def handle_update(self, message: messages.Update) -> None:
message.stop() message.stop()

View File

@@ -104,8 +104,8 @@ class Widget(DOMNode):
has_focus = Reactive(False) has_focus = Reactive(False)
descendant_has_focus = Reactive(False) descendant_has_focus = Reactive(False)
mouse_over = Reactive(False) mouse_over = Reactive(False)
scroll_x = Reactive(0.0, repaint=False) scroll_x = Reactive(0.0, repaint=False, layout=True)
scroll_y = Reactive(0.0, repaint=False) scroll_y = Reactive(0.0, repaint=False, layout=True)
scroll_target_x = Reactive(0.0, repaint=False) scroll_target_x = Reactive(0.0, repaint=False)
scroll_target_y = Reactive(0.0, repaint=False) scroll_target_y = Reactive(0.0, repaint=False)
show_vertical_scrollbar = Reactive(False, layout=True) show_vertical_scrollbar = Reactive(False, layout=True)
@@ -431,16 +431,13 @@ class Widget(DOMNode):
Returns: Returns:
bool: True if the scroll position changed, otherwise False. bool: True if the scroll position changed, otherwise False.
""" """
screen = self.screen
try: try:
widget_geometry = screen.find_widget(widget) widget_region = widget.content_region
container_geometry = screen.find_widget(self) container_region = self.content_region
except errors.NoWidget: except errors.NoWidget:
return False return False
widget_region = widget.content_region + widget_geometry.region.origin
container_region = self.content_region + container_geometry.region.origin
if widget_region in container_region: if widget_region in container_region:
# Widget is visible, nothing to do # Widget is visible, nothing to do
return False return False
@@ -610,10 +607,8 @@ class Widget(DOMNode):
@property @property
def content_region(self) -> Region: def content_region(self) -> Region:
"""A region relative to the Widget origin that contains the content.""" """Gets an absolute region containing the content (minus padding and border)."""
x, y = self.styles.content_gutter.top_left return self.region.shrink(self.styles.content_gutter)
width, height = self._container_size
return Region(x, y, width, height)
@property @property
def content_offset(self) -> Offset: def content_offset(self) -> Offset:

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timezone from datetime import datetime
import pytest import pytest
import time_machine import time_machine
@@ -6,22 +6,23 @@ from rich.align import Align
from rich.console import Console from rich.console import Console
from rich.segment import Segment from rich.segment import Segment
import msgpack
from tests.utilities.render import wait_for_predicate from tests.utilities.render import wait_for_predicate
from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice
TIMESTAMP = 1649166819 TIMESTAMP = 1649166819
WIDTH = 40 WIDTH = 40
# The string "Hello, world!" is encoded in the payload below # The string "Hello, world!" is encoded in the payload below
EXAMPLE_LOG = { _EXAMPLE_LOG = {
"type": "client_log", "type": "client_log",
"payload": { "payload": {
"encoded_segments": "gASVQgAAAAAAAABdlCiMDHJpY2guc2VnbWVudJSMB1NlZ" "segments": b"\x80\x04\x955\x00\x00\x00\x00\x00\x00\x00]\x94\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\rHello, world!\x94NN\x87\x94\x81\x94a.",
"21lbnSUk5SMDUhlbGxvLCB3b3JsZCGUTk6HlIGUaAOMAQqUTk6HlIGUZS4=",
"line_number": 123, "line_number": 123,
"path": "abc/hello.py", "path": "abc/hello.py",
"timestamp": TIMESTAMP, "timestamp": TIMESTAMP,
}, },
} }
EXAMPLE_LOG = msgpack.packb(_EXAMPLE_LOG)
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@@ -48,15 +49,10 @@ def test_log_message_render(console):
right: Align = right_cells[0] right: Align = right_cells[0]
# Since we can't guarantee the timezone the tests will run in... # Since we can't guarantee the timezone the tests will run in...
local_time = ( local_time = datetime.fromtimestamp(TIMESTAMP)
datetime.fromtimestamp(TIMESTAMP)
.replace(tzinfo=timezone.utc)
.astimezone(tz=datetime.now().astimezone().tzinfo)
)
timezone_name = local_time.tzname()
string_timestamp = local_time.time() string_timestamp = local_time.time()
assert left == f"[dim]{string_timestamp} {timezone_name}" assert left == f"[dim]{string_timestamp}"
assert right.align == "right" assert right.align == "right"
assert "hello.py:123" in right.renderable assert "hello.py:123" in right.renderable
@@ -69,7 +65,7 @@ def test_internal_message_render(console):
async def test_devtools_valid_client_log(devtools): async def test_devtools_valid_client_log(devtools):
await devtools.websocket.send_json(EXAMPLE_LOG) await devtools.websocket.send_bytes(EXAMPLE_LOG)
assert devtools.is_connected assert devtools.is_connected

View File

@@ -7,6 +7,7 @@ import time_machine
from aiohttp.web_ws import WebSocketResponse from aiohttp.web_ws import WebSocketResponse
from rich.console import ConsoleDimensions from rich.console import ConsoleDimensions
from rich.panel import Panel from rich.panel import Panel
import msgpack
from tests.utilities.render import wait_for_predicate from tests.utilities.render import wait_for_predicate
from textual.devtools.client import DevtoolsClient from textual.devtools.client import DevtoolsClient
@@ -27,36 +28,39 @@ async def test_devtools_client_is_connected(devtools):
assert devtools.is_connected assert devtools.is_connected
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) @time_machine.travel(datetime.utcfromtimestamp(TIMESTAMP))
async def test_devtools_log_places_encodes_and_queues_message(devtools): async def test_devtools_log_places_encodes_and_queues_message(devtools):
await devtools._stop_log_queue_processing() await devtools._stop_log_queue_processing()
devtools.log(DevtoolsLog("Hello, world!", CALLER)) devtools.log(DevtoolsLog("Hello, world!", CALLER))
queued_log = await devtools.log_queue.get() queued_log = await devtools.log_queue.get()
queued_log_json = json.loads(queued_log) queued_log_data = msgpack.unpackb(queued_log)
assert queued_log_json == { print(repr(queued_log_data))
assert queued_log_data == {
"type": "client_log", "type": "client_log",
"payload": { "payload": {
"timestamp": TIMESTAMP, "timestamp": 1649166819,
"path": CALLER_PATH, "path": "a/b/c.py",
"line_number": CALLER_LINENO, "line_number": 123,
"encoded_segments": "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWA0AAABIZWxsbywgd29ybGQhcQJOTodxA4FxBGgBWAEAAAAKcQVOTodxBoFxB2Uu", "segments": b"\x80\x04\x95B\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\rHello, world!\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.",
}, },
} }
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) @time_machine.travel(datetime.utcfromtimestamp(TIMESTAMP))
async def test_devtools_log_places_encodes_and_queues_many_logs_as_string(devtools): async def test_devtools_log_places_encodes_and_queues_many_logs_as_string(devtools):
await devtools._stop_log_queue_processing() await devtools._stop_log_queue_processing()
devtools.log(DevtoolsLog(("hello", "world"), CALLER)) devtools.log(DevtoolsLog(("hello", "world"), CALLER))
queued_log = await devtools.log_queue.get() queued_log = await devtools.log_queue.get()
queued_log_json = json.loads(queued_log) queued_log_data = msgpack.unpackb(queued_log)
assert queued_log_json == { print(repr(queued_log_data))
assert queued_log_data == {
"type": "client_log", "type": "client_log",
"payload": { "payload": {
"timestamp": TIMESTAMP, "timestamp": 1649166819,
"path": CALLER_PATH, "path": "a/b/c.py",
"line_number": CALLER_LINENO, "line_number": 123,
"encoded_segments": "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWAsAAABoZWxsbyB3b3JsZHECTk6HcQOBcQRoAVgBAAAACnEFTk6HcQaBcQdlLg==", "segments": b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\x0bhello world\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.",
}, },
} }

View File

@@ -4,12 +4,13 @@ from datetime import datetime
import time_machine import time_machine
import msgpack
from textual.devtools.redirect_output import StdoutRedirector from textual.devtools.redirect_output import StdoutRedirector
TIMESTAMP = 1649166819 TIMESTAMP = 1649166819
@time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) @time_machine.travel(datetime.utcfromtimestamp(TIMESTAMP))
async def test_print_redirect_to_devtools_only(devtools): async def test_print_redirect_to_devtools_only(devtools):
await devtools._stop_log_queue_processing() await devtools._stop_log_queue_processing()
@@ -19,14 +20,15 @@ async def test_print_redirect_to_devtools_only(devtools):
assert devtools.log_queue.qsize() == 1 assert devtools.log_queue.qsize() == 1
queued_log = await devtools.log_queue.get() queued_log = await devtools.log_queue.get()
queued_log_json = json.loads(queued_log) queued_log_data = msgpack.unpackb(queued_log)
payload = queued_log_json["payload"] print(repr(queued_log_data))
payload = queued_log_data["payload"]
assert queued_log_json["type"] == "client_log" assert queued_log_data["type"] == "client_log"
assert payload["timestamp"] == TIMESTAMP assert payload["timestamp"] == TIMESTAMP
assert ( assert (
payload["encoded_segments"] payload["segments"]
== "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWA0AAABIZWxsbywgd29ybGQhcQJOTodxA4FxBGgBWAEAAAAKcQVOTodxBoFxB2Uu" == b"\x80\x04\x95B\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\rHello, world!\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e."
) )
@@ -86,8 +88,10 @@ async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfil
assert queued_log_json["type"] == "client_log" assert queued_log_json["type"] == "client_log"
assert payload["timestamp"] == TIMESTAMP assert payload["timestamp"] == TIMESTAMP
assert payload[ assert (
"encoded_segments"] == "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWBQAAABIZWxsbyB3b3JsZCBtdWx0aXBsZXECTk6HcQOBcQRoAVgBAAAACnEFTk6HcQaBcQdlLg==" payload["encoded_segments"]
== "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWBQAAABIZWxsbyB3b3JsZCBtdWx0aXBsZXECTk6HcQOBcQRoAVgBAAAACnEFTk6HcQaBcQdlLg=="
)
assert len(payload["path"]) > 0 assert len(payload["path"]) > 0
assert payload["line_number"] != 0 assert payload["line_number"] != 0

View File

@@ -208,7 +208,6 @@ def test_animator():
animator() animator()
assert animate_test.foo == 0 assert animate_test.foo == 0
assert animator._on_animation_frame_called
animator._time = 5 animator._time = 5
animator() animator()

View File

@@ -31,26 +31,6 @@ async def test_focus_chain():
assert focused == ["foo", "Paul", "baz"] assert focused == ["foo", "Paul", "baz"]
async def test_show_focus():
app = App()
app.push_screen(Screen())
app.screen.add_children(
Focusable(id="foo"),
NonFocusable(id="bar"),
Focusable(Focusable(id="Paul"), id="container1"),
NonFocusable(Focusable(id="Jessica"), id="container2"),
Focusable(id="baz"),
)
focused = [widget.id for widget in app.focus_chain]
assert focused == ["foo", "Paul", "baz"]
assert app.focused is None
assert not app.has_class("-show-focus")
app.show_focus()
assert app.has_class("-show-focus")
async def test_focus_next_and_previous(): async def test_focus_next_and_previous():
app = App() app = App()