diff --git a/CHANGELOG.md b/CHANGELOG.md index 115a20b91c..e90a41c92d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `Style.has_transparent_foreground` property https://github.com/Textualize/textual/pull/5657 +### Fixed + +- Fixed TextArea's syntax highlighting. Some highlighting details were not being + applied. For example, in CSS, the text 'padding: 10px 0;' was shown in a + single colour. Now the 'px' appears in a different colour to the rest of the + text. + +- Fixed some situations where editing for syntax highlighed TextArea widgets with + large documents was very unresponsive. + + ## [2.1.2] - 2025-02-26 ### Fixed diff --git a/poetry.lock b/poetry.lock index f2200d7a03..513494286a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -17,6 +18,7 @@ version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, @@ -121,7 +123,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiohttp-jinja2" @@ -129,6 +131,7 @@ version = "1.6" description = "jinja2 template renderer for aiohttp.web (http server for asyncio)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "aiohttp-jinja2-1.6.tar.gz", hash = "sha256:a3a7ff5264e5bca52e8ae547bbfd0761b72495230d438d05b6c0915be619b0e2"}, {file = "aiohttp_jinja2-1.6-py3-none-any.whl", hash = "sha256:0df405ee6ad1b58e5a068a105407dc7dcc1704544c559f1938babde954f945c7"}, @@ -144,6 +147,7 @@ version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, @@ -158,6 +162,7 @@ version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, @@ -171,7 +176,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -180,6 +185,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -191,18 +198,19 @@ version = "25.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "babel" @@ -210,6 +218,7 @@ version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, @@ -219,7 +228,7 @@ files = [ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] -dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "black" @@ -227,6 +236,7 @@ version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, @@ -263,7 +273,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -273,6 +283,7 @@ version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, @@ -294,6 +305,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -305,6 +317,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -316,6 +329,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -417,6 +431,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -431,6 +446,7 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -442,6 +458,7 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -521,7 +538,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "distlib" @@ -529,6 +546,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -540,6 +558,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -554,6 +574,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -568,6 +589,7 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -576,7 +598,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "frozenlist" @@ -584,6 +606,7 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -685,6 +708,7 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -702,6 +726,7 @@ version = "4.0.12" description = "Git Object Database" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, @@ -716,6 +741,7 @@ version = "3.1.44" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, @@ -726,7 +752,7 @@ gitdb = ">=4.0.1,<5" [package.extras] doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "griffe" @@ -734,6 +760,7 @@ version = "0.32.3" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "griffe-0.32.3-py3-none-any.whl", hash = "sha256:d9471934225818bf8f309822f70451cc6abb4b24e59e0bb27402a45f9412510f"}, {file = "griffe-0.32.3.tar.gz", hash = "sha256:14983896ad581f59d5ad7b6c9261ff12bdaa905acccc1129341d13e545da8521"}, @@ -748,6 +775,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -759,6 +787,7 @@ version = "0.16.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, @@ -780,6 +809,7 @@ version = "0.23.3" description = "The next generation HTTP client." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, @@ -792,7 +822,7 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -803,6 +833,7 @@ version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, @@ -817,6 +848,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -831,6 +863,8 @@ version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -840,12 +874,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -854,6 +888,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -865,6 +900,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -879,6 +915,7 @@ version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, @@ -896,6 +933,7 @@ version = "2.0.3" description = "Links recognition library with FULL unicode support." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, @@ -916,6 +954,7 @@ version = "3.7" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, @@ -934,6 +973,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -960,6 +1000,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -1029,6 +1070,7 @@ version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, @@ -1048,6 +1090,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1059,6 +1102,7 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1070,6 +1114,7 @@ version = "1.6.1" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1093,7 +1138,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -1101,6 +1146,7 @@ version = "1.2.0" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, @@ -1117,6 +1163,7 @@ version = "1.0.2" description = "A mkdocs plugin that lets you exclude files or trees." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, ] @@ -1130,6 +1177,7 @@ version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, @@ -1147,6 +1195,7 @@ version = "1.3.0" description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_git_revision_date_localized_plugin-1.3.0-py3-none-any.whl", hash = "sha256:c99377ee119372d57a9e47cff4e68f04cce634a74831c06bc89b33e456e840a1"}, {file = "mkdocs_git_revision_date_localized_plugin-1.3.0.tar.gz", hash = "sha256:439e2f14582204050a664c258861c325064d97cdc848c541e48bb034a6c4d0cb"}, @@ -1169,6 +1218,7 @@ version = "9.6.4" description = "Documentation that simply works" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f"}, {file = "mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867"}, @@ -1198,6 +1248,7 @@ version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, @@ -1209,6 +1260,7 @@ version = "1.15.0" description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." optional = false python-versions = "<4,>=3.8" +groups = ["dev"] files = [ {file = "mkdocs_rss_plugin-1.15.0-py2.py3-none-any.whl", hash = "sha256:7308ac13f0976c0479db5a62cb7ef9b10fdd74b6521e459bb66a13e2cfe69d4b"}, {file = "mkdocs_rss_plugin-1.15.0.tar.gz", hash = "sha256:92995ed6c77b2ae1f5f2913e62282c27e50c35d618c4291b5b939e50badd7504"}, @@ -1233,6 +1285,7 @@ version = "0.20.0" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, @@ -1258,6 +1311,7 @@ version = "1.3.0" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocstrings_python-1.3.0-py3-none-any.whl", hash = "sha256:36c224c86ab77e90e0edfc9fea3307f7d0d245dd7c28f48bbb2203cf6e125530"}, {file = "mkdocstrings_python-1.3.0.tar.gz", hash = "sha256:f967f84bab530fcc13cc9c02eccf0c18bdb2c3bab5c55fa2045938681eec4fc4"}, @@ -1273,6 +1327,7 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -1346,6 +1401,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1450,6 +1506,7 @@ version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -1509,6 +1566,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1520,6 +1578,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1531,6 +1590,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1542,6 +1602,7 @@ version = "0.5.7" description = "Divides large result sets into pages for easier browsing" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, @@ -1557,6 +1618,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1568,6 +1630,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1584,6 +1647,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1599,6 +1663,7 @@ version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -1617,6 +1682,7 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -1724,6 +1790,7 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1738,6 +1805,7 @@ version = "10.14.3" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, @@ -1756,6 +1824,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1778,6 +1847,7 @@ version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, @@ -1796,6 +1866,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -1814,6 +1885,7 @@ version = "1.1.0" description = "Snapshot testing for Textual apps" optional = false python-versions = "<4.0.0,>=3.8.1" +groups = ["dev"] files = [ {file = "pytest_textual_snapshot-1.1.0-py3-none-any.whl", hash = "sha256:fdf7727d2bc444f947554308da1b08df7a45215fe49d0621cbbc24c33e8f7b8d"}, {file = "pytest_textual_snapshot-1.1.0.tar.gz", hash = "sha256:96d48ab01306852a3b4ae165f008d5fdd7fda777e91e9d2c3ea0f7d7458544eb"}, @@ -1832,6 +1904,7 @@ version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, @@ -1852,6 +1925,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1866,6 +1940,7 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -1877,6 +1952,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1939,6 +2015,7 @@ version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, @@ -1953,6 +2030,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2056,6 +2134,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2077,6 +2156,7 @@ version = "1.5.0" description = "Validating URI References per RFC 3986" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -2094,6 +2174,7 @@ version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main", "dev"] files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -2113,6 +2194,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2124,6 +2206,7 @@ version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, @@ -2135,6 +2218,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2146,6 +2230,7 @@ version = "4.8.0" description = "Pytest Snapshot Test Utility" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "syrupy-4.8.0-py3-none-any.whl", hash = "sha256:544f4ec6306f4b1c460fdab48fd60b2c7fe54a6c0a8243aeea15f9ad9c638c3f"}, {file = "syrupy-4.8.0.tar.gz", hash = "sha256:648f0e9303aaa8387c8365d7314784c09a6bab0a407455c6a01d6a4f5c6a8ede"}, @@ -2160,6 +2245,7 @@ version = "1.7.0" description = "Development tools for working with Textual" optional = false python-versions = "<4.0.0,>=3.8.1" +groups = ["dev"] files = [ {file = "textual_dev-1.7.0-py3-none-any.whl", hash = "sha256:a93a846aeb6a06edb7808504d9c301565f7f4bf2e7046d56583ed755af356c8d"}, {file = "textual_dev-1.7.0.tar.gz", hash = "sha256:bf1a50eaaff4cd6a863535dd53f06dbbd62617c371604f66f56de3908220ccd5"}, @@ -2179,6 +2265,7 @@ version = "1.1.1" description = "Turn your Textual TUIs in to web applications" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "textual_serve-1.1.1-py3-none-any.whl", hash = "sha256:568782f1c0e60e3f7039d9121e1cb5c2f4ca1aaf6d6bd7aeb833d5763a534cb2"}, {file = "textual_serve-1.1.1.tar.gz", hash = "sha256:71c662472c462e5e368defc660ee6e8eae3bfda88ca40c050c55474686eb0c54"}, @@ -2197,6 +2284,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2234,57 +2323,47 @@ files = [ [[package]] name = "tree-sitter" -version = "0.23.2" +version = "0.24.0" description = "Python bindings to the Tree-sitter parsing library" optional = true -python-versions = ">=3.9" -files = [ - {file = "tree-sitter-0.23.2.tar.gz", hash = "sha256:66bae8dd47f1fed7bdef816115146d3a41c39b5c482d7bad36d9ba1def088450"}, - {file = "tree_sitter-0.23.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3a937f5d8727bc1c74c4bf2a9d1c25ace049e8628273016ad0d45914ae904e10"}, - {file = "tree_sitter-0.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c7eae7fe2af215645a38660d2d57d257a4c461fe3ec827cca99a79478284e80"}, - {file = "tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a71d607595270b6870eaf778a1032d146b2aa79bfcfa60f57a82a7b7584a4c7"}, - {file = "tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe9b9ea7a0aa23b52fd97354da95d1b2580065bc12a4ac868f9164a127211d6"}, - {file = "tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d74d00a8021719eae14d10d1b1e28649e15d8b958c01c2b2c3dad7a2ebc4dbae"}, - {file = "tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6de18d8d8a7f67ab71f472d1fcb01cc506e080cbb5e13d52929e4b6fdce6bbee"}, - {file = "tree_sitter-0.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:12b60dca70d2282af942b650a6d781be487485454668c7c956338a367b98cdee"}, - {file = "tree_sitter-0.23.2-cp310-cp310-win_arm64.whl", hash = "sha256:3346a4dd0447a42aabb863443b0fd8c92b909baf40ed2344fae4b94b625d5955"}, - {file = "tree_sitter-0.23.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91fda41d4f8824335cc43c64e2c37d8089c8c563bd3900a512d2852d075af719"}, - {file = "tree_sitter-0.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92b2b489d5ce54b41f94c6f23fbaf592bd6e84dc2877048fd1cb060480fa53f7"}, - {file = "tree_sitter-0.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64859bd4aa1567d0d6016a811b2b49c59d4a4427d096e3d8c84b2521455f62b7"}, - {file = "tree_sitter-0.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:614590611636044e071d3a0b748046d52676dbda3bc9fa431216231e11dd98f7"}, - {file = "tree_sitter-0.23.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08466953c78ae57be61057188fb88c89791b0a562856010228e0ccf60e2ac453"}, - {file = "tree_sitter-0.23.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8a33f03a562de91f7fd05eefcedd8994a06cd44c62f7aabace811ad82bc11cbd"}, - {file = "tree_sitter-0.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:03b70296b569ef64f7b92b42ca5da9bf86d81bee2afd480bea35092687f51dae"}, - {file = "tree_sitter-0.23.2-cp311-cp311-win_arm64.whl", hash = "sha256:7cb4bb953ea7c0b50eeafc4454783e030357179d2a93c3dd5ebed2da5588ddd0"}, - {file = "tree_sitter-0.23.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a014498b6a9e6003fae8c6eb72f5927d62da9dcb72b28b3ce8cd15c6ff6a6572"}, - {file = "tree_sitter-0.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f8699b131d4bcbe3805c37e4ef3d159ee9a82a0e700587625623999ba0ea53"}, - {file = "tree_sitter-0.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4471577df285059c71686ecb208bc50fb472099b38dcc8e849b0e86652891e87"}, - {file = "tree_sitter-0.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f342c925290dd4e20ecd5787ef7ae8749981597ab364783a1eb73173efe65226"}, - {file = "tree_sitter-0.23.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4e9e53d07dd076bede72e4f7d3a0173d7b9ad6576572dd86da008a740a9bb22"}, - {file = "tree_sitter-0.23.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8caebe65bc358759dac2500d8f8feed3aed939c4ade9a684a1783fe07bc7d5db"}, - {file = "tree_sitter-0.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:fc5a72eb50d43485000dbbb309acb350467b7467e66dc747c6bb82ce63041582"}, - {file = "tree_sitter-0.23.2-cp312-cp312-win_arm64.whl", hash = "sha256:a0320eb6c7993359c5f7b371d22719ccd273f440d41cf1bd65dac5e9587f2046"}, - {file = "tree_sitter-0.23.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eff630dddee7ba05accb439b17e559e15ce13f057297007c246237ceb6306332"}, - {file = "tree_sitter-0.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4780ba8f3894f2dea869fad2995c2aceab3fd5ab9e6a27c45475d2acd7f7e84e"}, - {file = "tree_sitter-0.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b609460b8e3e256361fb12e94fae5b728cb835b16f0f9d590b5aadbf9d109b"}, - {file = "tree_sitter-0.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d070d8eaeaeb36cf535f55e5578fddbfc3bf53c1980f58bf1a99d57466b3b5"}, - {file = "tree_sitter-0.23.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878580b2ad5054c410ba3418edca4d34c81cc26706114d8f5b5541688bc2d785"}, - {file = "tree_sitter-0.23.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:29224bdc2a3b9af535b7725e249d3ee291b2e90708e82832e73acc175e40dc48"}, - {file = "tree_sitter-0.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:c58d89348162fbc3aea1fe6511a66ee189fc0e4e4bbe937026f29e4ecef17763"}, - {file = "tree_sitter-0.23.2-cp313-cp313-win_arm64.whl", hash = "sha256:0ff2037be5edab7801de3f6a721b9cf010853f612e2008ee454e0e0badb225a6"}, - {file = "tree_sitter-0.23.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a5db8e585205faef8bf219da77d8993e2ef04d08eda2e3c8ad7e4df8297ee344"}, - {file = "tree_sitter-0.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9dbd110a30cf28be5da734ae4cd0e9031768228dbf6a79f2973962aa51de4ec7"}, - {file = "tree_sitter-0.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569514b9a996a0fd458b3a891c46ca125298be0c03cf82f2b6f0c13d5d8f25dc"}, - {file = "tree_sitter-0.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a357ed98a74e47787b812df99a74a2c35c0fe11e55c2095cc01d1cad144ef552"}, - {file = "tree_sitter-0.23.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c2dfb8e8f760f4cc67888d03ef9e2dbd3353245f67f5efba375c2a14d944ac0e"}, - {file = "tree_sitter-0.23.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3ead958df87a21d706903987e665e9e0e5df7b2c5021ff69ea349826840adc6a"}, - {file = "tree_sitter-0.23.2-cp39-cp39-win_amd64.whl", hash = "sha256:611cae16be332213c0e6ece72c0bfca202e30ff320a8b309b1526c6cb79ee4ba"}, - {file = "tree_sitter-0.23.2-cp39-cp39-win_arm64.whl", hash = "sha256:b848e0fdd522fbb8888cdb4f4d93f8fad97ae10d70c122fb922e51363c7febcd"}, +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" +files = [ + {file = "tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734"}, + {file = "tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445"}, + {file = "tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831"}, + {file = "tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a"}, + {file = "tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5"}, + {file = "tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73"}, + {file = "tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95"}, + {file = "tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc"}, + {file = "tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13"}, + {file = "tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538"}, + {file = "tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd"}, + {file = "tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409"}, + {file = "tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921"}, + {file = "tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce"}, + {file = "tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2"}, + {file = "tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc"}, + {file = "tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4"}, + {file = "tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e"}, + {file = "tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e"}, + {file = "tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7"}, + {file = "tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751"}, + {file = "tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb"}, + {file = "tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071"}, + {file = "tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c"}, + {file = "tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad"}, + {file = "tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74"}, + {file = "tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9"}, + {file = "tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34"}, + {file = "tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8"}, ] [package.extras] -docs = ["sphinx (>=7.3,<8.0)", "sphinx-book-theme"] -tests = ["tree-sitter-html (>=0.23.0)", "tree-sitter-javascript (>=0.23.0)", "tree-sitter-json (>=0.23.0)", "tree-sitter-python (>=0.23.0)", "tree-sitter-rust (>=0.23.0)"] +docs = ["sphinx (>=8.1,<9.0)", "sphinx-book-theme"] +tests = ["tree-sitter-html (>=0.23.2)", "tree-sitter-javascript (>=0.23.1)", "tree-sitter-json (>=0.24.8)", "tree-sitter-python (>=0.23.6)", "tree-sitter-rust (>=0.23.2)"] [[package]] name = "tree-sitter-bash" @@ -2292,6 +2371,8 @@ version = "0.23.3" description = "Bash grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_bash-0.23.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c1ee7a46fcbfca9937d01056be756631762f53c5afdb8c4ab64eb9fed060896b"}, {file = "tree_sitter_bash-0.23.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a090118e887bf667d82ae445794906186216f5500e0d2cd58eb499f7502dc57"}, @@ -2312,6 +2393,8 @@ version = "0.23.2" description = "CSS grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_css-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:62b9eadb8f47c666a36a2ead96d17c2a01d7599e1f13f69c617f08e4acf62bf0"}, {file = "tree_sitter_css-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0be54e07f90679173bb06a8ecf483a7d79eaa6d236419b5baa6ce02401ea31a9"}, @@ -2332,6 +2415,8 @@ version = "0.23.4" description = "Go grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_go-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c9320f87a05cd47fa0f627b9329bbc09b7ed90de8fe4f5882aed318d6e19962d"}, {file = "tree_sitter_go-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:914e63d16b36ab0e4f52b031e574b82d17d0bbfecca138ae83e887a1cf5b71ac"}, @@ -2352,6 +2437,8 @@ version = "0.23.2" description = "HTML grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_html-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e1641d5edf5568a246c6c47b947ed524b5bf944664e6473b21d4ae568e28ee9"}, {file = "tree_sitter_html-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3d0a83dd6cd1c7d4bcf6287b5145c92140f0194f8516f329ae8b9e952fbfa8ff"}, @@ -2372,6 +2459,8 @@ version = "0.23.5" description = "Java grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df"}, {file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69"}, @@ -2392,6 +2481,8 @@ version = "0.23.1" description = "JavaScript grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6ca583dad4bd79d3053c310b9f7208cd597fd85f9947e4ab2294658bb5c11e35"}, {file = "tree_sitter_javascript-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:94100e491a6a247aa4d14caf61230c171b6376c863039b6d9cd71255c2d815ec"}, @@ -2412,6 +2503,8 @@ version = "0.24.8" description = "JSON grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab"}, {file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd"}, @@ -2432,6 +2525,8 @@ version = "0.3.2" description = "Markdown grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_markdown-0.3.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2a0d60ee5185fbc20c6f3e7744348956a62f8bc9ae85b574251632e3c2220c77"}, {file = "tree_sitter_markdown-0.3.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0a72f199966380e18f668abb3e9d0a75569c8a292967deefc432282e253f9f84"}, @@ -2453,6 +2548,8 @@ version = "0.23.6" description = "Python grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_python-0.23.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:28fbec8f74eeb2b30292d97715e60fac9ccf8a8091ce19b9d93e9b580ed280fb"}, {file = "tree_sitter_python-0.23.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:680b710051b144fedf61c95197db0094f2245e82551bf7f0c501356333571f7a"}, @@ -2473,6 +2570,8 @@ version = "0.24.3" description = "Regex grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_regex-0.24.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:16ded552d0f43dda608cec078b4a63f1dfa53c793775ba1a1bb06b2539b94fff"}, {file = "tree_sitter_regex-0.24.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0a26bf77f7a8aa070299246eb3a29276030481ff380346c4085a97e448c34570"}, @@ -2493,6 +2592,8 @@ version = "0.23.2" description = "Rust grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_rust-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b6b26a4c07ddc243f3701450ff34093b8e3b08f14d269db2d049c625d151677c"}, {file = "tree_sitter_rust-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c6224f608df559d75425e5ef428f635b9fb87d7aa8716444915ee67ec6955085"}, @@ -2513,6 +2614,8 @@ version = "0.3.7" description = "Tree-sitter Grammar for SQL" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_sql-0.3.7-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f3f8427328bd8b4ee02ab50d71bfc515937c037b8a03dcf54b8c98403d804269"}, {file = "tree_sitter_sql-0.3.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:97ad55611f7d777b08a30d60150e1c44100ac2759a341b1cf1ffa2f97f20259e"}, @@ -2533,6 +2636,8 @@ version = "0.7.0" description = "TOML grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_toml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b9ae5c3e7c5b6bb05299dd73452ceafa7fa0687d5af3012332afa7757653b676"}, {file = "tree_sitter_toml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:18be09538e9775cddc0290392c4e2739de2201260af361473ca60b5c21f7bd22"}, @@ -2553,6 +2658,8 @@ version = "0.7.0" description = "XML & DTD grammars for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc3e516d4c1e0860fb22172c172148debb825ba638971bc48bad15b22e5b0bae"}, {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0674fdf4cc386e4d323cb287d3b072663de0f20a9e9af5d5e09821aae56a9e5c"}, @@ -2573,6 +2680,8 @@ version = "0.7.0" description = "YAML grammar for tree-sitter" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"syntax\"" files = [ {file = "tree_sitter_yaml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e21553ac190ae05bf82796df8beb4d9158ba195b5846018cb36fbc3a35bd0679"}, {file = "tree_sitter_yaml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c022054f1f9b54201082ea83073a6c24c42d0436ad8ee99ff2574cba8f928c28"}, @@ -2593,6 +2702,7 @@ version = "67.8.0.0" description = "Typing stubs for setuptools" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "types-setuptools-67.8.0.0.tar.gz", hash = "sha256:95c9ed61871d6c0e258433373a4e1753c0a7c3627a46f4d4058c7b5a08ab844f"}, {file = "types_setuptools-67.8.0.0-py3-none-any.whl", hash = "sha256:6df73340d96b238a4188b7b7668814b37e8018168aef1eef94a3b1872e3f60ff"}, @@ -2604,6 +2714,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -2615,6 +2726,8 @@ version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["dev"] +markers = "python_version >= \"3.9\" and sys_platform == \"win32\"" files = [ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, @@ -2626,6 +2739,7 @@ version = "1.0.3" description = "Micro subset of unicode data files for linkify-it-py projects." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, @@ -2640,13 +2754,14 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2657,6 +2772,7 @@ version = "20.29.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, @@ -2669,7 +2785,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchdog" @@ -2677,6 +2793,7 @@ version = "4.0.2" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, @@ -2724,6 +2841,7 @@ version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, @@ -2836,23 +2954,25 @@ version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] syntax = ["tree-sitter", "tree-sitter-bash", "tree-sitter-css", "tree-sitter-go", "tree-sitter-html", "tree-sitter-java", "tree-sitter-javascript", "tree-sitter-json", "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-regex", "tree-sitter-rust", "tree-sitter-sql", "tree-sitter-toml", "tree-sitter-xml", "tree-sitter-yaml"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.8.1" -content-hash = "db29b377e8fcedd9730b54f573ba175c8743e06fa57da2dee8a4e62bc2a6faa7" +content-hash = "64a9f677e78ed910aa7679123dff96604f006ddc4f5c5fbba87ec8ebb642ec3d" diff --git a/pyproject.toml b/pyproject.toml index c01b22558b..bf053b703d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "Modern Text User Interface framework" authors = ["Will McGugan "] license = "MIT" readme = "README.md" -classifiers = [ +classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", @@ -52,24 +52,24 @@ typing-extensions = "^4.4.0" platformdirs = ">=3.6.0,<5" # start of [syntax] extras -# Require tree-sitter >= 0.23.0 and python >= 3.9 +# Require tree-sitter >= 0.24.0 and python >= 3.10 # Windows, MacOS and Linux binary wheels are available for all of the languages below. -tree-sitter = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-python = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-markdown = { version = ">=0.3.0", optional = true, python = ">=3.9"} -tree-sitter-json = { version = ">=0.24.0", optional = true, python = ">=3.9" } -tree-sitter-toml = { version = ">=0.6.0", optional = true, python = ">=3.9" } -tree-sitter-yaml = { version = ">=0.6.0", optional = true, python = ">=3.9" } -tree-sitter-html = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-css = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-javascript = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-rust = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-go = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-regex = { version = ">=0.24.0", optional = true, python = ">=3.9" } -tree-sitter-xml = { version = ">=0.7.0", optional = true, python = ">=3.9" } -tree-sitter-sql = { version = ">=0.3.0,<0.3.8", optional = true, python = ">=3.9" } -tree-sitter-java = { version = ">=0.23.0", optional = true, python = ">=3.9" } -tree-sitter-bash = { version = ">=0.23.0", optional = true, python = ">=3.9" } +tree-sitter = { version = ">=0.24.0", optional = true, python = ">=3.10" } +tree-sitter-python = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-markdown = { version = ">=0.3.0", optional = true, python = ">=3.10"} +tree-sitter-json = { version = ">=0.24.0", optional = true, python = ">=3.10" } +tree-sitter-toml = { version = ">=0.6.0", optional = true, python = ">=3.10" } +tree-sitter-yaml = { version = ">=0.6.0", optional = true, python = ">=3.10" } +tree-sitter-html = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-css = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-javascript = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-rust = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-go = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-regex = { version = ">=0.24.0", optional = true, python = ">=3.10" } +tree-sitter-xml = { version = ">=0.7.0", optional = true, python = ">=3.10" } +tree-sitter-sql = { version = ">=0.3.0,<0.3.8", optional = true, python = ">=3.10" } +tree-sitter-java = { version = ">=0.23.0", optional = true, python = ">=3.10" } +tree-sitter-bash = { version = ">=0.23.0", optional = true, python = ">=3.10" } # end of [syntax] extras [tool.poetry.extras] diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 76103ec47c..c975aeb3d0 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -174,7 +174,7 @@ def __init__( Args: chops: A mapping of offsets to list of segments, per line. - crop: Region to restrict update to. + spans: Line spans to restrict update to. chop_ends: A list of the end offsets for each line """ self.chops = chops @@ -263,8 +263,8 @@ def render_segments(self, console: Console) -> str: append(strip.render(console)) continue - strip = strip.crop(0, min(end, x2) - x) - append(move_to(x, y).segment.text) + strip = strip.crop(x1, min(end, x2) - x) + append(move_to(x1, y).segment.text) append(strip.render(console)) if y != last_y: @@ -1229,6 +1229,7 @@ def update_widgets(self, widgets: set[Widget]) -> None: add_region = regions.append get_widget = self.visible_widgets.__getitem__ for widget in self.visible_widgets.keys() & widgets: + widget._prepare_for_repaint() region, clip = get_widget(widget) offset = region.offset intersection = clip.intersection diff --git a/src/textual/app.py b/src/textual/app.py index 6861012863..49802a335f 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -314,8 +314,8 @@ class App(Generic[ReturnType], DOMNode): scrollbar-background-active: ansi_default; scrollbar-color: ansi_blue; scrollbar-color-active: ansi_bright_blue; - scrollbar-color-hover: ansi_bright_blue; - scrollbar-corner-color: ansi_default; + scrollbar-color-hover: ansi_bright_blue; + scrollbar-corner-color: ansi_default; } .bindings-table--key { @@ -336,18 +336,18 @@ class App(Generic[ReturnType], DOMNode): } /* When a widget is maximized */ - Screen.-maximized-view { + Screen.-maximized-view { layout: vertical !important; hatch: right $panel; overflow-y: auto !important; align: center middle; .-maximized { - dock: initial !important; + dock: initial !important; } } /* Fade the header title when app is blurred */ - &:blur HeaderTitle { - text-opacity: 50%; + &:blur HeaderTitle { + text-opacity: 50%; } } *:disabled:can-focus { @@ -399,7 +399,7 @@ class MyApp(App[None]): ALLOW_SELECT: ClassVar[bool] = True """A switch to toggle arbitrary text selection for the app. - + Note that this doesn't apply to Input and TextArea which have builtin support for selection. """ @@ -445,7 +445,7 @@ class MyApp(App[None]): """The default value of [Screen.ALLOW_IN_MAXIMIZED_VIEW][textual.screen.Screen.ALLOW_IN_MAXIMIZED_VIEW].""" CLICK_CHAIN_TIME_THRESHOLD: ClassVar[float] = 0.5 - """The maximum number of seconds between clicks to upgrade a single click to a double click, + """The maximum number of seconds between clicks to upgrade a single click to a double click, a double click to a triple click, etc.""" BINDINGS: ClassVar[list[BindingType]] = [ @@ -472,7 +472,7 @@ class MyApp(App[None]): ESCAPE_TO_MINIMIZE: ClassVar[bool] = True """Use escape key to minimize widgets (potentially overriding bindings). - + This is the default value, used if the active screen's `ESCAPE_TO_MINIMIZE` is not changed from `None`. """ @@ -544,7 +544,7 @@ def __init__( self._registered_themes: dict[str, Theme] = {} """Themes that have been registered with the App using `App.register_theme`. - + This excludes the built-in themes.""" for theme in BUILTIN_THEMES.values(): @@ -746,7 +746,7 @@ def __init__( self.theme_changed_signal: Signal[Theme] = Signal(self, "theme-changed") """Signal that is published when the App's theme is changed. - + Subscribers will receive the new theme object as an argument to the callback. """ diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py index 47e87eb09b..2dcd9bee5c 100644 --- a/src/textual/document/_document.py +++ b/src/textual/document/_document.py @@ -3,12 +3,13 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from functools import lru_cache -from typing import TYPE_CHECKING, NamedTuple, Tuple, overload +from itertools import zip_longest +from typing import TYPE_CHECKING, Callable, NamedTuple, Tuple, overload from typing_extensions import Literal, get_args if TYPE_CHECKING: - from tree_sitter import Node, Query + from tree_sitter import Query from textual._cells import cell_len from textual.geometry import Size @@ -27,6 +28,10 @@ class EditResult: """The new end Location after the edit is complete.""" replaced_text: str """The text that was replaced.""" + dirty_lines: range | None = None + """The range of lines considered dirty.""" + alt_dirty_line: tuple[int, range] | None = None + """Alternative list of lines considered dirty.""" @lru_cache(maxsize=1024) @@ -140,27 +145,32 @@ def get_size(self, indent_width: int) -> Size: The Size of the document bounding box. """ - def query_syntax_tree( - self, - query: "Query", - start_point: tuple[int, int] | None = None, - end_point: tuple[int, int] | None = None, - ) -> dict[str, list["Node"]]: - """Query the tree-sitter syntax tree. + def clean_up(self) -> None: + """Perform any pre-deletion clean up. - The default implementation always returns an empty list. + The default implementation does nothing. + """ + + def set_syntax_tree_update_callback( + callback: Callable[[], None], + ) -> None: + """Set a callback function for signalling a rebuild of the syntax tree. - To support querying in a subclass, this must be implemented. + The default implementation does nothing. Args: - query: The tree-sitter Query to perform. - start_point: The (row, column byte) to start the query at. - end_point: The (row, column byte) to end the query at. + callback: A function that takes no arguments and returns None. + """ - Returns: - A dict mapping captured node names to lists of Nodes with that name. + def trigger_syntax_tree_update(self, force_update: bool = False) -> None: + """Trigger a new syntax tree update to run in the background. + + The default implementation does nothing. + + Args: + force_update: When set, ensure that the syntax tree is regenerated + unconditionally. """ - return {} def prepare_query(self, query: str) -> "Query | None": return None @@ -235,6 +245,10 @@ def newline(self) -> Newline: """Get the Newline used in this document (e.g. '\r\n', '\n'. etc.)""" return self._newline + def copy_of_lines(self): + """Provide a copy of the document's lines.""" + return list(self._lines) + def get_size(self, tab_width: int) -> Size: """The Size of the document, taking into account the tab rendering width. @@ -294,11 +308,40 @@ def replace_range(self, start: Location, end: Location, text: str) -> EditResult destination_column = len(before_selection) insert_lines = [before_selection + after_selection] + try: + prev_top_line = lines[top_row] + except IndexError: + prev_top_line = None lines[top_row : bottom_row + 1] = insert_lines destination_row = top_row + len(insert_lines) - 1 end_location = (destination_row, destination_column) - return EditResult(end_location, replaced_text) + + n_previous_lines = bottom_row - top_row + 1 + dirty_range = None + alt_dirty_line = None + if len(insert_lines) != n_previous_lines: + dirty_range = range(top_row, len(lines)) + else: + if len(insert_lines) == 1 and prev_top_line is not None: + rng = self._build_single_line_range(prev_top_line, insert_lines[0]) + if rng is not None: + alt_dirty_line = top_row, rng + else: + dirty_range = range(top_row, bottom_row + 1) + + return EditResult(end_location, replaced_text, dirty_range, alt_dirty_line) + + @staticmethod + def _build_single_line_range(a, b): + rng = [] + for i, (ca, cb) in enumerate(zip_longest(a, b)): + if ca != cb: + rng.append(i) + if rng: + return range(rng[0], rng[-1] + 1) + else: + None def get_text_range(self, start: Location, end: Location) -> str: """Get the text that falls between the start and end locations. diff --git a/src/textual/document/_document_navigator.py b/src/textual/document/_document_navigator.py index 6dbc3c6d59..ee69aca79f 100644 --- a/src/textual/document/_document_navigator.py +++ b/src/textual/document/_document_navigator.py @@ -2,6 +2,8 @@ from bisect import bisect, bisect_left, bisect_right from typing import Any, Sequence +from rich.cells import get_character_cell_size + from textual._cells import cell_len from textual.document._document import Location from textual.document._wrapped_document import WrappedDocument @@ -242,6 +244,16 @@ def get_location_left(self, location: Location) -> Location: length_of_row_above = len(self._document[row - 1]) target_row = row if column != 0 else row - 1 target_column = column - 1 if column != 0 else length_of_row_above + + if target_row < self._document.line_count: + line = self._document[target_row] + if target_column < len(line): + while target_column > 0: + c = line[target_column] + if c == "\t" or get_character_cell_size(c) > 0: + break + target_column -= 1 + return target_row, target_column def get_location_right(self, location: Location) -> Location: @@ -263,6 +275,15 @@ def get_location_right(self, location: Location) -> Location: is_end_of_line = self.is_end_of_document_line(location) target_row = row + 1 if is_end_of_line else row target_column = 0 if is_end_of_line else column + 1 + + if target_row < self._document.line_count: + line = self._document[target_row] + while target_column < len(line): + c = line[target_column] + if c == "\t" or get_character_cell_size(c) > 0: + break + target_column += 1 + return target_row, target_column def get_location_above(self, location: Location) -> Location: diff --git a/src/textual/document/_syntax_aware_document.py b/src/textual/document/_syntax_aware_document.py index 00305dcec2..9734c176d0 100644 --- a/src/textual/document/_syntax_aware_document.py +++ b/src/textual/document/_syntax_aware_document.py @@ -1,7 +1,11 @@ from __future__ import annotations +import weakref +from asyncio import CancelledError, Event, Task, create_task, sleep +from typing import Callable, NamedTuple + try: - from tree_sitter import Language, Node, Parser, Query, Tree + from tree_sitter import Language, Parser, Query, Tree TREE_SITTER = True except ImportError: @@ -11,6 +15,17 @@ from textual.document._document import Document, EditResult, Location, _utf8_encode +class SyntaxTreeEdit(NamedTuple): + """Details of a tree-sitter syntax tree edit operation.""" + + start_byte: int + old_end_byte: int + new_end_byte: int + start_point: int + old_end_point: int + new_end_point: int + + class SyntaxAwareDocumentError(Exception): """General error raised when SyntaxAwareDocument is used incorrectly.""" @@ -44,9 +59,38 @@ def __init__( self._parser = Parser(self.language) """The tree-sitter Parser or None if tree-sitter is unavailable.""" - self._syntax_tree: Tree = self._parser.parse(self._read_callable) # type: ignore + self._syntax_tree: Tree = self._parser.parse( + self.text.encode("utf-8") + ) # type: ignore """The tree-sitter Tree (syntax tree) built from the document.""" + self._syntax_tree_update_callback: Callable[[], None] | None = None + self._background_parser = BackgroundSyntaxParser(self) + self._pending_syntax_edits: list[SyntaxTreeEdit] = [] + + @property + def current_syntax_tree(self) -> Tree: + """The current syntax tree.""" + return self._syntax_tree + + def clean_up(self) -> None: + """Perform any pre-deletion clean up.""" + self._background_parser.stop() + + def apply_pending_syntax_edits(self) -> bool: + """Apply any pending edits to the syntax tree. + + Returns: + True if any edits were applied. + """ + if self._pending_syntax_edits: + for edit in self._pending_syntax_edits: + self._syntax_tree.edit(**edit._asdict()) + self._pending_syntax_edits[:] = [] + return True + else: + return False + def prepare_query(self, query: str) -> Query | None: """Prepare a tree-sitter tree query. @@ -62,34 +106,25 @@ def prepare_query(self, query: str) -> Query | None: """ return self.language.query(query) - def query_syntax_tree( + def set_syntax_tree_update_callback( self, - query: Query, - start_point: tuple[int, int] | None = None, - end_point: tuple[int, int] | None = None, - ) -> dict[str, list["Node"]]: - """Query the tree-sitter syntax tree. + callback: Callable[[], None], + ) -> None: + """Set a callback function for signalling a rebuild of the syntax tree. - The default implementation always returns an empty list. + Args: + callback: A function that takes no arguments and returns None. + """ + self._syntax_tree_update_callback = callback - To support querying in a subclass, this must be implemented. + def trigger_syntax_tree_update(self, force_update: bool = False) -> None: + """Trigger a new syntax tree update to run in the background. Args: - query: The tree-sitter Query to perform. - start_point: The (row, column byte) to start the query at. - end_point: The (row, column byte) to end the query at. - - Returns: - A tuple containing the nodes and text captured by the query. + force_update: When set, ensure that the syntax tree is regenerated + unconditionally. """ - captures_kwargs = {} - if start_point is not None: - captures_kwargs["start_point"] = start_point - if end_point is not None: - captures_kwargs["end_point"] = end_point - - captures = query.captures(self._syntax_tree.root_node, **captures_kwargs) - return captures + self._background_parser.trigger_syntax_tree_update(force_update) def replace_range(self, start: Location, end: Location, text: str) -> EditResult: """Replace text at the given range. @@ -117,22 +152,53 @@ def replace_range(self, start: Location, end: Location, text: str) -> EditResult end_location = replace_result.end_location assert self._syntax_tree is not None assert self._parser is not None - self._syntax_tree.edit( - start_byte=start_byte, - old_end_byte=old_end_byte, - new_end_byte=start_byte + text_byte_length, - start_point=start_point, - old_end_point=old_end_point, - new_end_point=self._location_to_point(end_location), - ) - # Incrementally parse the document. - self._syntax_tree = self._parser.parse( - self._read_callable, - self._syntax_tree, # type: ignore[arg-type] + self._pending_syntax_edits.append( + SyntaxTreeEdit( + start_byte=start_byte, + old_end_byte=old_end_byte, + new_end_byte=start_byte + text_byte_length, + start_point=start_point, + old_end_point=old_end_point, + new_end_point=self._location_to_point(end_location), + ) ) - return replace_result + def reparse(self, timeout_us: int, text: bytes) -> bool: + """Reparse the document. + + Args: + timeout_us: The parser timeout in microseconds. + lines: A list of the lines being parsed. + + Returns: + True if parsing succeeded and False if a timeout occurred. + """ + assert timeout_us > 0 + tree = self._syntax_tree + saved_timeout = self._parser.timeout_micros + try: + self._parser.timeout_micros = timeout_us + try: + tree = self._parser.parse(text, tree) # type: ignore[arg-type] + except ValueError: + # The only known cause is a timeout. + return False + else: + self._syntax_tree = tree + if self._syntax_tree_update_callback is not None: + + def set_new_tree(): + self._syntax_tree = tree + + changed_ranges = self._syntax_tree.changed_ranges(tree) + self._syntax_tree_update_callback(self._syntax_tree) + else: + self._syntax_tree = tree + return True + finally: + self._parser.timeout_micros = saved_timeout + def get_line(self, index: int) -> str: """Return the string representing the line, not including new line characters. @@ -188,41 +254,74 @@ def _location_to_point(self, location: Location) -> tuple[int, int]: bytes_on_left = 0 return row, bytes_on_left - def _read_callable(self, byte_offset: int, point: tuple[int, int]) -> bytes: - """A callable which informs tree-sitter about the document content. - This is passed to tree-sitter which will call it frequently to retrieve - the bytes from the document. +class BackgroundSyntaxParser: + """A provider of incremental background parsing for syntax highlighting. - Args: - byte_offset: The number of (utf-8) bytes from the start of the document. - point: A tuple (row index, column *byte* offset). Note that this differs - from our Location tuple which is (row_index, column codepoint offset). + This runs tree-sitter parsing as a parallel, background asyncio task. This + prevents occasional, relatively long parsing times from making `TextArea` + editing become unresponsive. + """ - Returns: - All the utf-8 bytes between the byte_offset/point and the end of the current - line _including_ the line separator character(s). Returns None if the - offset/point requested by tree-sitter doesn't correspond to a byte. - """ - row, column = point - lines = self._lines - newline = self.newline + PARSE_TIME_SLICE = 0.005 + PARSE_TIMEOUT_MICROSECONDS = int(PARSE_TIME_SLICE * 1_000_000) - row_out_of_bounds = row >= len(lines) - if row_out_of_bounds: - return b"" - else: - row_text = lines[row] + def __init__(self, document: SyntaxAwareDocument): + self._document_ref = weakref.ref(document) + self._event = Event() + self._task: Task = create_task(self._execute_reparsing()) + self._force_update = False - encoded_row = _utf8_encode(row_text) - encoded_row_length = len(encoded_row) + def stop(self): + """Stop running as a background task.""" + self._task.cancel() - if column < encoded_row_length: - return encoded_row[column:] + _utf8_encode(newline) - elif column == encoded_row_length: - return _utf8_encode(newline[0]) - elif column == encoded_row_length + 1: - if newline == "\r\n": - return b"\n" + def trigger_syntax_tree_update(self, force_update: bool) -> None: + """Trigger a new syntax tree update to run in the background. - return b"" + Args: + force_update: When set, ensure that the syntax tree is regenerated + unconditionally. + """ + if force_update: + self._force_update = True + self._event.set() + + async def _execute_reparsing(self) -> None: + """Run, as a task, tree-sitter reparse operations on demand.""" + while True: + try: + try: + await self._event.wait() + except Exception as e: + return + self._event.clear() + force_update = self._force_update + self._force_update = False + await self._perform_a_single_reparse(force_update) + except CancelledError: + return + + async def _perform_a_single_reparse(self, force_update: bool) -> None: + document = self._document_ref() + if document is None: + return + if not (document.apply_pending_syntax_edits() or force_update): + return + + # In order to allow the user to continue editing without interruption, we reparse + # a snapshot of the TextArea's document. + copy_of_text_for_parsing = document.text.encode("utf-8") + + # Use tree-sitter's parser timeout mechanism to break the full reparse + # into multiple steps. Most of the time, tree-sitter is so fast that no + # looping occurs. + parsed_ok = False + while not parsed_ok: + parsed_ok = document.reparse( + self.PARSE_TIMEOUT_MICROSECONDS, text=copy_of_text_for_parsing + ) + if not parsed_ok: + # Sleeping for zero seconds allows other tasks, I/O, *etc.* to execute, + # keeping the TextArea and other widgets responsive. + await sleep(0.0) diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index 68846bedab..1cbf77e69e 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -1,6 +1,7 @@ from __future__ import annotations from bisect import bisect_right +from functools import lru_cache from rich.text import Text @@ -85,6 +86,7 @@ def wrap(self, width: int, tab_width: int | None = None) -> None: tab_width: The maximum width to consider for tab characters. If None, reuse the tab width. """ + self.get_sections.cache_clear() self._width = width if tab_width: self._tab_width = tab_width @@ -151,7 +153,7 @@ def lines(self) -> list[list[str]]: @property def height(self) -> int: """The height of the wrapped document.""" - return sum(len(offsets) + 1 for offsets in self._wrap_offsets) + return len(self._offset_to_line_info) def wrap_range( self, @@ -168,6 +170,7 @@ def wrap_range( old_end: The old end location of the edit in document-space. new_end: The new end location of the edit in document-space. """ + self.get_sections.cache_clear() start_line_index, _ = start old_end_line_index, _ = old_end new_end_line_index, _ = new_end @@ -403,6 +406,7 @@ def get_target_document_column( return target_column_index + @lru_cache(200) def get_sections(self, line_index: int) -> list[str]: """Return the sections for the given line index. diff --git a/src/textual/drivers/_writer_thread.py b/src/textual/drivers/_writer_thread.py index a26ef46fbb..aa2dbc9f1f 100644 --- a/src/textual/drivers/_writer_thread.py +++ b/src/textual/drivers/_writer_thread.py @@ -8,6 +8,8 @@ MAX_QUEUED_WRITES: Final[int] = 30 +write_count = 0 + class WriterThread(threading.Thread): """A thread / file-like to do writes to stdout in the background.""" @@ -23,6 +25,8 @@ def write(self, text: str) -> None: Args: text: Text to write to the file. """ + global write_count + write_count += len(text) self._queue.put(text) def isatty(self) -> bool: diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 9847052fa8..e7d18c9326 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -1315,6 +1315,91 @@ def grow_maximum(self, other: Spacing) -> Spacing: ) +class LineRegion(NamedTuple): + """Defines a region for a line. + + This is similar to a `Region`, but its height is fixed at 1. + + ``` + (x, y) + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–² + β”‚ β”‚ height = 1 + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό + ◀─────── width ──────▢ + ``` + + A region may be empty (w == 0) in which case the 'x' value is considered + irrelevant. + """ + + x: int = 0 + """Offset in the x-axis (horizontal).""" + y: int = 0 + """Offset in the y-axis (vertical).""" + width: int = 0 + """The width of the region.""" + + def __bool__(self) -> bool: + """A Region is considered False when it has zero width.""" + return self.width > 0 + + @lru_cache(maxsize=1024) + def union(self, region: LineRegion) -> LineRegion: + """Get the smallest line region that contains both regions. + + Both regions must have the same 'y' value. + + Args: + region: Another line region. + + Returns: + An optimally sized region to cover both regions. + """ + assert self.y == region.y + + if self.width == 0: + return region + elif region.width == 0: + return self + + x_min = min(self.x, region.x) + x_max = max(self.x + self.width, region.x + region.width) + return LineRegion(x_min, self.y, x_max - x_min) + + @lru_cache(maxsize=1024) + def as_region(self, xoffset: int) -> Region: + x, y, w = self + return Region(x + xoffset, y, w, 1) + + @lru_cache(maxsize=1024) + def disjoint_regions(self, region: LineRegion) -> list[LineRegion]: + assert self.y == region.y + + if self.x == region.x and self.width == region.width: + return [] + + self_x2 = self.x + self.width - 1 + region_x2 = region.x + region.width - 1 + if self_x2 + 1 == region.x or region_x2 + 1 == self.x: + # The regions abut, so return the union.A + return [self.union(region)] + + if self_x2 + 1 < region.x or region_x2 + 1 < self.x: + # The regions do not overlap at all so return boith regions. + return [self, region] + + regions = [] + w = abs(self.x - region.x) + if w != 0: + regions.append(LineRegion(min(self.x, region.x), self.y, w)) + w = abs(self_x2 - region_x2) + if w != 0: + x2 = max(self_x1, region_x2) + x = x2 - w + 1 + regions.append(LineRegion(x, self.y, w)) + return regions + + NULL_OFFSET: Final = Offset(0, 0) """An [offset][textual.geometry.Offset] constant for (0, 0).""" diff --git a/src/textual/screen.py b/src/textual/screen.py index 1268858e22..780d5ae5a1 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -154,8 +154,8 @@ class Screen(Generic[ScreenResultType], Widget): Screen { layout: vertical; overflow-y: auto; - background: $background; - + background: $background; + &:inline { height: auto; min-height: 1; @@ -167,7 +167,7 @@ class Screen(Generic[ScreenResultType], Widget): background: ansi_default; color: ansi_default; - &.-screen-suspended { + &.-screen-suspended { text-style: dim; ScrollBar { text-style: not dim; @@ -175,7 +175,7 @@ class Screen(Generic[ScreenResultType], Widget): } } .screen--selection { - background: $primary 50%; + background: $primary 50%; } } """ @@ -1811,7 +1811,7 @@ class ModalScreen(Screen[ScreenResultType]): overflow-y: auto; background: $background 60%; &:ansi { - background: transparent; + background: transparent; } } """ diff --git a/src/textual/strip.py b/src/textual/strip.py index dc69561a5b..50c06d9682 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -7,11 +7,17 @@ from __future__ import annotations +from functools import lru_cache from itertools import chain from typing import Any, Iterable, Iterator, Sequence import rich.repr -from rich.cells import cell_len, set_cell_size +from rich.cells import ( + _is_single_cell_widths, + cell_len, + get_character_cell_size, + set_cell_size, +) from rich.console import Console, ConsoleOptions, RenderResult from rich.measure import Measurement from rich.segment import Segment @@ -525,7 +531,7 @@ def crop(self, start: int, end: int | None = None) -> Strip: for segment in iter_segments: end_pos = pos + _cell_len(segment.text) if end_pos > start: - segment = segment.split_cells(start - pos)[1] + segment = self.split_segment_cells(segment, start - pos)[1] break pos = end_pos @@ -542,7 +548,7 @@ def crop(self, start: int, end: int | None = None) -> Strip: if end_pos < end: add_segment(segment) else: - add_segment(segment.split_cells(end - pos)[0]) + add_segment(self.split_segment_cells(segment, end - pos)[0]) break pos = end_pos segment = next(iter_segments, None) @@ -550,6 +556,84 @@ def crop(self, start: int, end: int | None = None) -> Strip: self._crop_cache[cache_key] = strip return strip + # TODO: + # This and _split_cells are replacements for Rich library methods. + # Arguably, the Rich library should be updated with the same 'fixes'. + @classmethod + def split_segment_cells(cls, segment, cut: int) -> Tuple["Segment", "Segment"]: + """Split segment into two segments at the specified column. + + If the cut point falls in the middle of a 2-cell wide character then it is replaced + by two spaces, to preserve the display width of the parent segment. + + Returns: + Tuple[Segment, Segment]: Two segments. + """ + text, style, control = segment + + if _is_single_cell_widths(text): + # Fast path with all 1 cell characters + if cut >= len(text): + return segment, Segment("", style, control) + return ( + Segment(text[:cut], style, control), + Segment(text[cut:], style, control), + ) + + return cls._split_cells(segment, cut) + + @classmethod + @lru_cache(1024 * 16) + def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]: + + def split_text_at_cell_boundary(text, index): + text_end = len(text) - 1 + while index < text_end: + if cell_size(text[index]) > 0: + break + index += 1 + return text[:index], text[index:] + + text, style, control = segment + _Segment = Segment + + cell_length = segment.cell_length + if cut >= cell_length: + # Cut beyond end of segment, return segment and empty segment. + return segment, _Segment("", style, control) + + # If some simple maths lands us on the correct cell then 'take the + # win'. + cell_size = get_character_cell_size + pos = int((cut / cell_length) * (len(text) - 1)) + before, after = split_text_at_cell_boundary(text, pos) + cell_pos = cell_len(before) + if cell_pos == cut: + return ( + _Segment(before, style, control), + _Segment(text[pos:], style, control), + ) + + # Fall back to the slowish iteration over characters. + while pos < len(text): + char = text[pos] + pos += 1 + cell_pos += cell_size(char) + if cell_pos == cut: + before, after = split_text_at_cell_boundary(text, pos) + return ( + _Segment(before, style, control), + _Segment(after, style, control), + ) + if cell_pos > cut: + before = text[:pos] + return ( + _Segment(before[: pos - 1] + " ", style, control), + _Segment(" " + text[pos:], style, control), + ) + + raise AssertionError("Will never reach here") + def divide(self, cuts: Iterable[int]) -> Sequence[Strip]: """Divide the strip into multiple smaller strips by cutting at given (cell) indices. diff --git a/src/textual/tree-sitter/highlights/python.scm b/src/textual/tree-sitter/highlights/python.scm index 6f844b6135..3e7381b813 100644 --- a/src/textual/tree-sitter/highlights/python.scm +++ b/src/textual/tree-sitter/highlights/python.scm @@ -11,9 +11,9 @@ ;; Identifier naming conventions ((identifier) @type - (#lua-match? @type "^[A-Z].*[a-z]")) + (#match? @type "^[A-Z].*[a-z]")) ((identifier) @constant - (#lua-match? @constant "^[A-Z][A-Z_0-9]*$")) + (#match? @constant "^[A-Z][A-Z_0-9]*$")) ((attribute attribute: (identifier) @field) @@ -59,12 +59,12 @@ ((call function: (identifier) @constructor) - (#lua-match? @constructor "^[A-Z]")) + (#match? @constructor "^[A-Z]")) ((call function: (attribute attribute: (identifier) @constructor)) - (#lua-match? @constructor "^[A-Z]")) + (#match? @constructor "^[A-Z]")) ;; Decorators diff --git a/src/textual/widget.py b/src/textual/widget.py index 04557156df..243d573110 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -309,11 +309,11 @@ class Widget(DOMNode): ALLOW_MAXIMIZE: ClassVar[bool | None] = None """Defines default logic to allow the widget to be maximized. - + - `None` Use default behavior (Focusable widgets may be maximized) - `False` Do not allow widget to be maximized - `True` Allow widget to be maximized - + """ ALLOW_SELECT: ClassVar[bool] = True @@ -2437,6 +2437,18 @@ def _set_dirty(self, *regions: Region) -> None: if outer_size: self._repaint_regions.add(outer_size.region) + def _prepare_for_repaint(self) -> None: + """A hook to allow a widget to prepare for a repaint. + + Currently only used by `TextArea`. This hook allow a widget a chance to + perform extra preparation required in order to be ready to repaint. + + The hook is for specialised use and should almost certainly not be + used for most widgets. In the case of the `TextArea`, it is used to + calculate the regions that have changed since the last repaint - + something that is automatic for other widgets. + """ + def _exchange_repaint_regions(self) -> Collection[Region]: """Get a copy of the regions which need a repaint, and clear internal cache. @@ -4074,6 +4086,17 @@ def refresh( self.check_idle() return self + def _trigger_repaint(self) -> None: + """Indicate that parts of this widget need repainting. + + !!!warning + + This is provided for very specialised use - currently only the + `TextArea` widget uses it. + """ + self._repaint_required = True + self.check_idle() + def remove(self) -> AwaitRemove: """Remove the Widget from the DOM (effectively deleting it). diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 7c16716ed5..646d9e9e9f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -2,12 +2,25 @@ import dataclasses import re +from array import array from collections import defaultdict +from contextlib import contextmanager from dataclasses import dataclass from functools import lru_cache +from itertools import chain from pathlib import Path -from typing import TYPE_CHECKING, ClassVar, Iterable, Optional, Sequence, Tuple +from typing import ( + TYPE_CHECKING, + ClassVar, + ContextManager, + Iterable, + MutableSequence, + Optional, + Sequence, + Tuple, +) +from rich.cells import get_character_cell_size from rich.console import RenderableType from rich.style import Style from rich.text import Text @@ -22,7 +35,6 @@ EditResult, Location, Selection, - _utf8_encode, ) from textual.document._document_navigator import DocumentNavigator from textual.document._edit import Edit @@ -32,7 +44,7 @@ SyntaxAwareDocumentError, ) from textual.document._wrapped_document import WrappedDocument -from textual.expand_tabs import expand_tabs_inline, expand_text_tabs_from_widths +from textual.expand_tabs import expand_tabs_inline from textual.screen import Screen if TYPE_CHECKING: @@ -42,7 +54,7 @@ from textual._cells import cell_len, cell_width_to_column_index from textual.binding import Binding from textual.events import Message, MouseEvent -from textual.geometry import Offset, Region, Size, Spacing, clamp +from textual.geometry import LineRegion, Offset, Region, Size, Spacing, clamp from textual.reactive import Reactive, reactive from textual.scroll_view import ScrollView from textual.strip import Strip @@ -51,6 +63,11 @@ _CLOSING_BRACKETS = {v: k for k, v in _OPENING_BRACKETS.items()} _TREE_SITTER_PATH = Path(__file__).parent / "../tree-sitter/" _HIGHLIGHTS_PATH = _TREE_SITTER_PATH / "highlights/" +DISCARDABLE_CONTROL_CHARS: set[str] = set( + chr(n) for n in chain(range(0x09), range(0x10, 0x20), range(0x7F, 0xA0)) +) +TAB = "\t" +EMPTY_RANGE = range(0) StartColumn = int EndColumn = Optional[int] @@ -77,6 +94,15 @@ ] """Languages that are included in the `syntax` extras.""" +# This is a pragmatic limitation, which allows some code to be simplified and +# other code to use less memory. This is basically 2**32 so lines of 2**31 less +# a few bytes are technically supported, but I (Paul Ollis) have no intention +# of writing the tests. +# +# This constant can be elimiated at the expense of handling extra corner cases +# in the code and not using array('L') in place of lists. +MAX_LINE_LENGTH_X2 = 0x1_0000_0000 + class ThemeDoesNotExist(Exception): """Raised when the user tries to use a theme which does not exist. @@ -90,6 +116,572 @@ class LanguageDoesNotExist(Exception): """ +class TextReprString(str): + """A string 'optimised' for text representation. + + This extends the standard Python 'str' providing support for easily working + with character cells. + + Use the `create` class method to instantiate instances. + """ + + __slots__ = ["_cell_offsets", "_cells", "_tab_offsets"] + + def __new__(cls, value: str): + # Create the basic instance. the `create` class method is responsible + # for assigning the additional attributes. + inst = super().__new__(cls, value) + return inst + + @classmethod + @lru_cache(maxsize=256) + def create(cls, text: str, tab_size: int = 4, tab_offset: int = 0): + """Create a TextReprString from the given text. + + Any TAB characters in `text` are expanded before the instance is + created. Expansion is performed with respect to character cells, taking + into account wide characters and zero width combining characters. + Leading zero width characters, all non-TAB control characters and + combining characters immediately after TAB characters are discarded. + + Args: + text: The text of the string. + tab_size: The tab-stop size for the string. + offset: The offset to the first tabstop position. Zero implies + tab_size. + """ + text, cells, cell_offsets, tab_offsets = cls._expand_and_map_string( + text, tab_size, tab_offset + ) + inst = cls(text) + inst._cell_offsets = cell_offsets + inst._cells = cells + inst._tab_offsets = tab_offsets + return inst + + @property + def cells(self) -> list[tuple[str, ...]]: + """A list of character tuples for this string's terminal representation. + + Most cells will consist of a 1-tuple holding a single unicode + character. For double width characters, the first cell is a 1-tuple of + the character and the following cell is an empty tuple. For TAB + characters, a 1-tuple of (`\t`,) is followed by zero or more empty + tuples. When a character is followed by one or more combining (zero + width) characters, the cell is a tuple of the character and those + combining characters. + """ + return self._cells + + def cell_width(self, index) -> int: + """The number of cells covered by the character at a given index. + + When the index lies on an expanded TAB character, the result is + number of spaces used in the expansion. + """ + try: + cell_index = self._cell_offsets[index] + n = len(self._cells[cell_index]) + except IndexError: + return 1 + else: + if n == 0: + return 0 + cell_index += 1 + while cell_index < len(self._cells): + chars = self._cells[cell_index] + if chars: + break + n += 1 + cell_index += 1 + return n + + def logical_character_width(self, index) -> int: + """The logical width of the character at a given index. + + For TAB characters this is the number of spaces that the TAB has been + expannded to. For all other characters this returns 1. + """ + try: + cell_index = self._cell_offsets[index] + cell = self._cells[cell_index] + except IndexError: + return 1 + if cell == (TAB,): + return self.cell_width(index) + else: + return 1 + + def render_count(self, index) -> int: + """How many characters combine into the cell for the character index. + + Typically this is 1, but if a character is followed by one or more + combining (zero width) characters the value is increased accordingly. + """ + try: + cell_index = self._cell_offsets[index] + return len(self._cells[cell_index]) + except IndexError: + return 1 + + def cell_offset(self, index) -> int: + """Calculate the cell offset for the indexed character.""" + try: + return self._cell_offsets[index] + except IndexError: + return self._cell_offsets[-1] + + def adjust_index_for_tabs(self, index) -> int: + """Adjust index to allow for TAB character expansion.""" + try: + return self._tab_offsets[index] + except IndexError: + return len(self) + + @staticmethod + def _expand_and_map_string( + text: str, + tab_size: int, + tab_offset: int, + ) -> tuple[str, bytearray, bytearray]: + """Expand TAB characters in text and generate useful mappings. + + Args: + text: The text of the string. + tab_size: The tab-stop size for the string. + offset: An offset to the first tabstop position. + Returns: + A tuple of: + + 1. The expanded string. + 2. A character cell representation of the string. + 3. Offsets that allow mapping string indices to cell indices. + 4. Offsets that map from the original string to the expanded string. + """ + expanded_text: list[str] = [] + cell_offsets = array("L") + tab_offsets = array("L") + cells: list[tuple[str, ...]] = [] + + next_tab_offset = tab_size if tab_offset == 0 else tab_offset + char_index = 0 + cell_index = 0 + discarding_zero_width = True + empty_tuple = () + + for c in text: + if discarding_zero_width and character_cell_size(c) == 0: + # Discard invalid zero width/control characters. + continue + + if c in DISCARDABLE_CONTROL_CHARS: + continue + + discarding_zero_width = False + tab_offsets.append(char_index) + if c == TAB: + # Replace with a space and add additional spaces so that the + # next character's cell is tab-aligned. + expanded_text.append(" ") + cells.append((TAB,)) + cell_offsets.append(cell_index) + char_index += 1 + cell_index += 1 + while cell_index < next_tab_offset: + expanded_text.append(" ") + cells.append(empty_tuple) + cell_offsets.append(cell_index) + cell_index += 1 + char_index += 1 + discarding_zero_width = False + + else: + width = character_cell_size(c) + if width == 0: + cells[-1] = cells[-1] + (c,) + else: + expanded_text.append(c) + cells.append((c,)) + assert width == 1 or width == 2 + if width == 2: + cells.append(()) + + cell_offsets.append(cell_index) + char_index += 1 + cell_index += character_cell_size(c) + + if cell_index >= next_tab_offset: + next_tab_offset += tab_size + + # Add a cell offset for the 'cursor-at-end-of-line' position. + cell_offsets.append(char_index) + return "".join(expanded_text), cells, cell_offsets, tab_offsets + + +class DocSelection: + + def __init__(self, selection: Selection, lines: list[str]): + start, end = selection.start, selection.end + self.selection = Selection(*sorted((start, end))) + self.cursor = end + self.lines = lines + self.line_range = range(selection.start[0], selection.end[0] + 1) + + def char_range(self, index) -> range: + """The range of characters covered on a given line.""" + lines = self.lines + if index in self.line_range and index < len(lines): + selection = self.selection + line = lines[index] + if len(self.line_range) == 1: + return range(selection.start[1], selection.end[1] + 1) + elif index == selection.start[0]: + return range(selection.start[1], len(line)) + elif index == selection.end[0]: + return range(0, min(selection.end[1] + 1, len(line))) + else: + return range(0, len(line)) + else: + return range(0) + + def __len__(self) -> int: + return len(self.line_range) + + +class HighlightMap: + """Lazy evaluated pseudo dictionary mapping lines to highlight information. + + This represents a snapshot of the TextArea's underlying document. + + Args: + text_area_widget: The associated `TextArea` widget. + """ + + BLOCK_SIZE = 50 + + def __init__(self): + self._lines: list[str] = [] + """The lines from which the syntax tree was generated.""" + + self._tree: Tree | None = None + """The tree-sitter tree from which to genrate highlights.""" + + self._query: Query | None = None + """The tree-sitter query used to generate highlights from the tree.""" + + self._highlighted_blocks: set[int] = set() + """The set of already highlighted blocks, BLOCK_SIZE lines per block.""" + + self._highlights: dict[int, list[Highlight]] = defaultdict(list) + """A cache mapping line index to a list of Highlight instances.""" + + self.tab_size: int = 4 + """The tab size setting in effect.""" + + @property + def line_count(self) -> int: + """The number of lines in the map.""" + return len(self._lines) + + def copy(self) -> HighlightMap: + """Create a copy of this highlight map.""" + inst = HighlightMap() + inst._lines = self._lines + inst._tree = self._tree + inst._query = self._query + inst._highlighted_blocks = self._highlighted_blocks.copy() + inst._highlights = self._highlights.copy() + return inst + + def set_snapshot( + self, lines: list[str], query: Query, tree: Tree, tab_size: int + ) -> None: + """Set the snapshot information for this mapping + + Args: + lines: The lines from which the syntax tree was generated. + query: The current Query structure used to generate highlights. + tree: The tree-sitter syntax tree. + tab_size: The tab_size for the text area. + .""" + if self._tree is not tree: + self._highlights.clear() + self._highlighted_blocks.clear() + self._query = query + self._tree = tree + self.tab_size = tab_size + self._lines = [] if None in (query, tree) else lines + + def set_query(self, query: Query) -> None: + self._query = query + self._highlights.clear() + self._highlighted_blocks.clear() + + def difference_region( + self, index: int, other_map: HighlightMap + ) -> LineRegion | None: + """Create a region by comparing highlights for a line with another map. + + Args: + index: The index of the line to compare. + other_map: The other HighlightMap to use for comparison. + + Returns: + None if the highlights are the same. Otherwise a Region + covering the full extent of the changes. + """ + highlights = self[index] + other_highlighs = other_map[index] + if highlights != other_highlighs: + min_start = min( + highlight[0] for highlight in chain(highlights, other_highlighs) + ) + max_end = max( + highlight[1] for highlight in chain(highlights, other_highlighs) + ) + return LineRegion(min_start, index, max_end - min_start + 1) + else: + return None + + @staticmethod + def _highlight_change_range(highlight_list_a, highlight_list_b) -> range: + min_start = min( + highlight[0] for highlight in chain(highlight_list_a, highlight_list_b) + ) + max_end = max( + highlight[1] for highlight in chain(highlight_list_a, highlight_list_b) + ) + return range(min_start, max_end + 1) + + def __getitem__(self, index: int) -> list[Highlight]: + if index >= self.line_count: + return [] + + block_index = index // self.BLOCK_SIZE + if block_index not in self._highlighted_blocks: + self._highlighted_blocks.add(block_index) + self._build_part_of_highlight_map(block_index * self.BLOCK_SIZE) + return self._highlights[index] + + def _build_part_of_highlight_map(self, start_index: int) -> None: + """Build part of the highlight map. + + This is invoped by __getitem__, when an uncached highlight list is + accessed. It generates the highlights for the block of lines containing + the index and adds them to the cache. + + Args: + start_index: The start of the block of lines for which to build the + map. + """ + start_point = (start_index, 0) + end_index = min(self.line_count, start_index + self.BLOCK_SIZE) + end_point = (end_index, 0) + with temporary_query_point_range(self._query, start_point, end_point): + captures = self._query.captures(self._tree.root_node) + + highlights = self._highlights + line_count = len(self._lines) + for highlight_name, nodes in captures.items(): + for node in nodes: + node_start_row, node_start_column = node.start_point + node_end_row, node_end_column = node.end_point + if node_start_row == node_end_row: + if node_start_row < line_count: + highlight = node_start_column, node_end_column, highlight_name + highlights[node_start_row].append(highlight) + else: + # Add the first line of the node range + if node_start_row < line_count: + highlights[node_start_row].append( + (node_start_column, None, highlight_name) + ) + + # Add the middle lines - entire row of this node is highlighted + middle_highlight = (0, None, highlight_name) + for node_row in range(node_start_row + 1, node_end_row): + if node_row < line_count: + highlights[node_row].append(middle_highlight) + + # Add the last line of the node range + if node_end_row < line_count: + highlights[node_end_row].append( + (0, node_end_column, highlight_name) + ) + + def realign_highlight(highlight): + start, end, name = highlight + return mapper[start], mapper[end], name + + # Tree-sitter uses byte offsets, but we want to use characters so we + # adjust each highlight's offset here to match character positions in + # the (TAB expanded) line text. + block_end = min(line_count, start_index + self.BLOCK_SIZE) + for index in range(start_index, block_end): + # text, offsets = expand_tabs(self._lines[index]) + text = self._lines[index] + mapper = build_byte_to_tab_expanded_string_table(text, self.tab_size) + highlights[index][:] = [ + realign_highlight(highlight) for highlight in highlights[index] + ] + + # The highlights for each line need to be sorted. Each highlight is of + # the form: + # + # a, b, highlight-name + # + # Where a is a number and b is a number or ``None``. These highlights need + # to be sorted in ascending order of ``a``. When two highlights have the same + # value of ``a`` then the one with the larger a--b range comes first, with ``None`` + # being considered larger than any number. + def sort_key(highlight: Highlight) -> tuple[int, int, int]: + a, b, _ = highlight + max_range_index = 1 + if b is None: + max_range_index = 0 + b = a + return a, max_range_index, a - b + + for line_index in range(start_index, end_index): + highlights.get(line_index, []).sort(key=sort_key) + + +@dataclass +class PreRenderLine: + """Pre-render information about a physical line in a TextArea. + + This stores enough information to compute difference regions. + """ + + text: TextReprString + """The plain form of the text on the line.""" + gutter_text: str + """The text shown in the gutter.""" + select_range: range + """The range of characters highlighted by the current selection.""" + syntax: list[Highlight] + """The syntax highlights for the line.""" + cursor_highlighted: bool + """Set if cursor highlighting is shown for this physical line.""" + + def make_diff_regions( + self, + text_area: TextArea, + y: int, + line: PreRenderLine | None, + full_width: int, + gutter_width: int, + prev_gutter_width: int, + ) -> list[LineRegion]: + text_width = full_width - gutter_width + if line is None: + return [LineRegion(0, y, text_width)] + + def text_region(x, width): + return LineRegion(x + gutter_width, y, width) + + regions = [] + if self.cursor_highlighted != line.cursor_highlighted: + regions.append(LineRegion(0, y, text_width + gutter_width)) + return regions + + if self.text != line.text: + regions.append( + build_difference_region( + y, + self.text.cells, + line.text.cells, + x_offset=gutter_width, + ) + ) + + if self.gutter_text != line.gutter_text: + text_a = text_b = "" + if gutter_width > 1: + gutter_width_no_margin = gutter_width - 2 + text_a = f"{self.gutter_text:>{gutter_width_no_margin}}" + if prev_gutter_width > 1: + gutter_width_no_margin = prev_gutter_width - 2 + text_b = f"{line.gutter_text:>{gutter_width_no_margin}}" + regions.append(build_difference_region(y, text_a, text_b)) + + if self.select_range != line.select_range: + before, common, after = intersect_ranges( + self.select_range, line.select_range + ) + if before: + regions.append(text_region(before.start, len(before))) + if after: + regions.append(text_region(after.start, len(after))) + + if self.syntax != line.syntax: + min_start = min(hl[0] for hl in chain(self.syntax, line.syntax)) + max_end = max(hl[1] for hl in chain(self.syntax, line.syntax)) + regions.append(text_region(min_start, max_end - min_start + 1)) + + return regions + + +@dataclass +class PreRenderState: + """Information about the TextArea state at the just before being rendered. + + Attributes: + lines: A snapshot of the document's lines at the time of rendering. + cursor: A tuple of (visible, cell_x, cell_y, width, char_x). + size: The (width, height) of the full text area, including the gutter. + gutter_width: The width of the gutter. + bracket: A tuple of (cell_x, cell_y, char_x). + """ + + lines: list[PreRenderLine] + cursor: tuple[bool, int, int, int] = (False, -1, -1, 1) + size: tuple[int, int] = 0, 0 + gutter_width: int = 0 + bracket: tuple[int, int, int] = -1, -1, -1 + + def make_diff_regions( + self, + text_area: TextArea, + state: PreRenderState, + ) -> list[LineRegion]: + + def text_region(x, y, width): + return LineRegion(x + gutter_width, y, width) + + gutter_width = self.gutter_width + prev_gutter_width = state.gutter_width + regions = [] + for y, line in enumerate(self.lines): + try: + old_line = state.lines[y] + except IndexError: + old_line = None + if not (old_line == line): + regions.extend( + line.make_diff_regions( + text_area, + y, + old_line, + self.size.width, + gutter_width, + prev_gutter_width, + ) + ) + + if self.cursor != state.cursor: + if self.cursor[1] >= 0: + regions.append(text_region(*self.cursor[1:-1])) + if state.cursor[1] >= 0: + regions.append(text_region(*state.cursor[1:-1])) + if self.bracket != state.bracket: + if self.bracket[0] >= 0: + regions.append(text_region(*self.bracket[:-1], 1)) + if state.bracket[0] >= 0: + regions.append(text_region(*state.bracket[:-1], 1)) + return regions + + @dataclass class TextAreaLanguage: """A container for a language which has been registered with the TextArea.""" @@ -323,7 +915,10 @@ class TextArea(ScrollView): """ selection: Reactive[Selection] = reactive( - Selection(), init=False, always_update=True + Selection(), + init=False, + always_update=True, + repaint=False, ) """The selection start and end locations (zero-based line_index, offset). @@ -473,15 +1068,15 @@ def __init__( cursor is currently at. If the cursor is at a bracket, or there's no matching bracket, this will be `None`.""" - self._highlights: dict[int, list[Highlight]] = defaultdict(list) - """Mapping line numbers to the set of highlights for that line.""" - self._highlight_query: "Query | None" = None """The query that's currently being used for highlighting.""" self.document: DocumentBase = Document(text) """The document this widget is currently editing.""" + self._highlights: HighlightMap = HighlightMap() + """Mapping of line number to the lists of highlights.""" + self.wrapped_document: WrappedDocument = WrappedDocument(self.document) """The wrapped view of the document.""" @@ -492,6 +1087,10 @@ def __init__( self._cursor_offset = (0, 0) """The virtual offset of the cursor (not screen-space offset).""" + self._old_render_state: PreRenderState = PreRenderState(lines=[]) + self._new_render_state: PreRenderState = PreRenderState(lines=[]) + """Saved state for the last time this widget was rendered.""" + self._set_document(text, language) self.language = language @@ -609,36 +1208,30 @@ def check_consume_key(self, key: str, character: str | None = None) -> bool: # Otherwise we capture all printable keys return character is not None and character.isprintable() - def _build_highlight_map(self) -> None: - """Query the tree for ranges to highlights, and update the internal highlights mapping.""" - highlights = self._highlights - highlights.clear() - if not self._highlight_query: - return + def _handle_syntax_tree_update(self, tree: Tree) -> None: + """Reflect changes to the syntax tree.""" + self._trigger_repaint() - captures = self.document.query_syntax_tree(self._highlight_query) - for highlight_name, nodes in captures.items(): - for node in nodes: - node_start_row, node_start_column = node.start_point - node_end_row, node_end_column = node.end_point + def _handle_change_affecting_highlighting( + self, + force_update: bool = False, + ) -> None: + """Trigger an update of the syntax highlighting tree. - if node_start_row == node_end_row: - highlight = (node_start_column, node_end_column, highlight_name) - highlights[node_start_row].append(highlight) - else: - # Add the first line of the node range - highlights[node_start_row].append( - (node_start_column, None, highlight_name) - ) + If the tree is already being updated in the background then that will + complete first. - # Add the middle lines - entire row of this node is highlighted - for node_row in range(node_start_row + 1, node_end_row): - highlights[node_row].append((0, None, highlight_name)) + Args: + force_update: When set, ensure that the syntax tree is regenerated + unconditionally. + """ + self.document.trigger_syntax_tree_update(force_update=force_update) - # Add the last line of the node range - highlights[node_end_row].append( - (0, node_end_column, highlight_name) - ) + def _reset_highlights(self) -> None: + """Reset the lazily evaluated highlight map.""" + + if self._highlight_query: + self._highlights.reset() def _watch_has_focus(self, focus: bool) -> None: self._cursor_visible = focus @@ -664,6 +1257,9 @@ def _watch_selection( self.scroll_cursor_visible() + if previous_selection != selection: + self.post_message(self.SelectionChanged(selection, self)) + cursor_row, cursor_column = cursor_location try: @@ -674,13 +1270,8 @@ def _watch_selection( # Record the location of a matching closing/opening bracket. match_location = self.find_matching_bracket(character, cursor_location) self._matching_bracket_location = match_location - if match_location is not None: - _, offset_y = self._cursor_offset - self.refresh_lines(offset_y) - self.app.cursor_position = self.cursor_screen_offset - if previous_selection != selection: - self.post_message(self.SelectionChanged(selection, self)) + self._trigger_repaint() def _watch_cursor_blink(self, blink: bool) -> None: if not self.is_mounted: @@ -898,6 +1489,8 @@ def update_highlight_query(self, name: str, highlight_query: str) -> None: # it could be a different highlight query for the same language. if name == self.language: self._set_document(self.text, name) + if self._highlight_query: + self._highlights.set_query(self._highlight_query) def _set_document(self, text: str, language: str | None) -> None: """Construct and return an appropriate document. @@ -939,6 +1532,9 @@ def _set_document(self, text: str, language: str | None) -> None: ) else: self._highlight_query = document.prepare_query(highlight_query) + document.set_syntax_tree_update_callback( + self._handle_syntax_tree_update + ) elif language and not TREE_SITTER: # User has supplied a language i.e. `TextArea(language="python")`, but they # don't have tree-sitter available in the environment. We fallback to plain text. @@ -957,10 +1553,12 @@ def _set_document(self, text: str, language: str | None) -> None: # Use a regular plain-text document. document = Document(text) + if self.document: + self.document.clean_up() self.document = document self.wrapped_document = WrappedDocument(document, tab_width=self.indent_width) self.navigator = DocumentNavigator(self.wrapped_document) - self._build_highlight_map() + self._handle_change_affecting_highlighting(force_update=True) self.move_cursor((0, 0)) self._rewrap_and_refresh_virtual_size() @@ -1063,12 +1661,17 @@ def _yield_character_locations_reverse( def _refresh_size(self) -> None: """Update the virtual size of the TextArea.""" - if self.soft_wrap: - self.virtual_size = Size(0, self.wrapped_document.height) - else: - # +1 width to make space for the cursor resting at the end of the line - width, height = self.document.get_size(self.indent_width) - self.virtual_size = Size(width + self.gutter_width + 1, height) + dirty = self._dirty_regions.copy(), self._repaint_regions.copy() + try: + if self.soft_wrap: + self.virtual_size = Size(0, self.wrapped_document.height) + else: + # +1 width to make space for the cursor resting at the end of the line + width, height = self.document.get_size(self.indent_width) + self.virtual_size = Size(width + self.gutter_width + 1, height) + finally: + self._dirty_regions, self._repaint_regions = dirty + self._trigger_repaint() def get_line(self, line_index: int) -> Text: """Retrieve the line at the given line index. @@ -1085,222 +1688,405 @@ def get_line(self, line_index: int) -> Text: line_string = self.document.get_line(line_index) return Text(line_string, end="") - def render_line(self, y: int) -> Strip: - """Render a single line of the TextArea. Called by Textual. + def _prepare_for_repaint(self) -> Collection[Region]: + with self._preserved_refresh_state(): + return self._do_prepare_for_repaint() + + def _do_prepare_for_repaint(self) -> Collection[Region]: + is_syntax_aware = self.is_syntax_aware + if is_syntax_aware: + highlights = self._highlights + highlights.set_snapshot( + lines=self.document.copy_of_lines(), + query=self._highlight_query, + tree=self.document.current_syntax_tree, + tab_size=self.indent_width, + ) - Args: - y: Y Coordinate of line relative to the widget region. + prev_render_state = self._new_render_state + render_state = self._pre_render_lines() + regions = render_state.make_diff_regions(self, prev_render_state) + self._old_render_state = render_state - Returns: - A rendered line. - """ - theme = self._theme - if theme: - theme.apply_css(self) + self._repaint_regions.clear() + self._dirty_regions.clear() - wrapped_document = self.wrapped_document - scroll_x, scroll_y = self.scroll_offset + regions = [r.as_region(0) for r in regions if r.width > 0] + if regions: + self.refresh(*regions) - # Account for how much the TextArea is scrolled. - y_offset = y + scroll_y + def render_lines(self, crop: Region) -> list[Strip]: + if len(self._dirty_regions) == 0: + self._new_render_state = self._old_render_state + return super().render_lines(crop) - # If we're beyond the height of the document, render blank lines - out_of_bounds = y_offset >= wrapped_document.height + ret = super().render_lines(crop) + self._dirty_regions.clear() - if out_of_bounds: - return Strip.blank(self.size.width) + self._new_render_state = self._old_render_state - # Get the line corresponding to this offset + return ret + + @contextmanager + def _preserved_refresh_state(self) -> ContextManager: + repaint_required = self._repaint_required try: - line_info = wrapped_document._offset_to_line_info[y_offset] - except IndexError: - line_info = None + yield + finally: + self._repaint_required = repaint_required + + def _pre_render_lines(self) -> PreRenderState: + + def build_doc_y_to_render_y_table() -> dict[int, int]: + """Build a dict mapping document y (row) to text area y.""" + if not visible_line_range: + return [] + + line_index, section_offset = offset_to_line_info[scroll_y] + line_count = self.document.line_count + table: dicxt[int, int] = {} + y = -section_offset + while line_index < line_count: + table[line_index] = y + y += len(wrap_offsets[line_index]) + 1 + if y not in visible_line_range: + break + line_index += 1 + + return table + + def doc_pos_to_xy(row, column) -> tuple[int, int]: + try: + y = doc_y_to_render_y_table[row] + except KeyError: + return -1, -1 + + offsets = wrap_offsets[row] + offset = 0 + physical_column = column + for offset in offsets: + if column >= offset: + y += 1 + physical_column = column - offset + else: + break + return physical_column, y + + def snip_syntax(line_highlights, start, end): + snipped = [] + for hl_start, hl_end, hl_name in line_highlights: + if hl_end is None: + hl_end = end + hl_start = max(start, hl_start) - start + hl_end = min(end, hl_end) - start + if hl_start <= hl_end: + snipped.append((hl_start, hl_end, hl_name)) + return snipped + + def add_pre_renders(y: int) -> None: + + nonlocal cursor_line_text + + def get_line_sections(section_offset): + sections = get_sections(line_index) + if not soft_wrap: + text = sections[0] + if scroll_x > 0: + # If horizontally scrolled, 'pretend' this line is the + # second of a wrapped line. + section_offset = 1 + sections = [ + text[:scroll_x], + text[scroll_x : text_width + scroll_x], + ] + else: + sections = [text[:text_width]] + return sections, section_offset + + def calculate_selection_limits() -> tuple[int | None, int | None]: + """Calculate the selection limits for the current document line.""" + sel_start = sel_end = None + if line_index in selection_line_range: + if len(selection_line_range) == 1: + sel_start = selection_top_column + sel_len = selection_bottom_column - selection_top_column + 1 + elif line_index == selection_top_row: + sel_start = selection_top_column + sel_len = MAX_LINE_LENGTH_X2 + elif line_index == selection_bottom_row: + sel_start = 0 + sel_len = selection_bottom_column + else: + sel_start = 0 + sel_len = MAX_LINE_LENGTH_X2 + sel_end = sel_start + sel_len + return sel_start, sel_end + + def create_gutter_text(): + if gutter_width > 0: + if section_offset == 0 or not soft_wrap: + gutter_content = str(line_index + line_number_start) + gutter_text = f"{gutter_content:>{gutter_width_no_margin}} " + else: + gutter_text = blank_gutter_text + else: + gutter_text = blank_gutter_text + return gutter_text + + def create_select_range() -> range: + """Calculate the selection range for the current line section.""" + + nonlocal sel_start, sel_end, line_text + + select_range = EMPTY_RANGE + if sel_start is not None: + if sel_start <= sel_end and 0 <= sel_start <= text_length: + range_end = min(sel_end, text_length) + if not line_text and yy != cursor_y: + # Make sure that empty line show up as selected. + line_text = TextReprString.create("β–Œ") + select_range = range(1, 0, -1) + elif sel_start < sel_end: + # The selection covers part of this line section. + select_range = range(sel_start, range_end) + + # Adjust start/end ready for the next line section. + sel_start = max(0, sel_start - text_length) + sel_end = max(0, sel_end - text_length) + + return select_range + + y_offset = y + scroll_y + if y_offset >= len(offset_to_line_info): + repr_line = TextReprString.create("") + text_repr_strings[y] = repr_line + rendered_lines.append(PreRenderLine(repr_line, "", range(0), [], False)) + return - if line_info is None: - return Strip.blank(self.size.width) + line_index, section_offset = offset_to_line_info[y_offset] + sections, section_offset = get_line_sections(section_offset) + sel_start, sel_end = calculate_selection_limits() + gutter_text = create_gutter_text() + doc_line_highlights = highlights[line_index] if highlights else [] + + syn_start = 0 + line_highlights = [] + tab_offset = 0 + for yy, doc_text in enumerate(sections, y - section_offset): + line_text = TextReprString.create( + doc_text, self.indent_width, tab_offset=tab_offset + ) + tab_offset = self.indent_width - len(line_text) % self.indent_width + text_length = len(line_text) + syn_end = syn_start + len(line_text) + if yy in visible_line_range and yy not in text_repr_strings: + select_range = create_select_range() + if highlights: + line_highlights = snip_syntax( + doc_line_highlights, syn_start, syn_end + ) + if yy == cursor_y: + cursor_line_text = line_text + rendered_lines.append( + PreRenderLine( + line_text, + gutter_text, + select_range, + line_highlights, + cursor_row == line_index, + ) + ) + text_repr_strings[yy] = line_text + gutter_text = blank_gutter_text + syn_start = syn_end - line_index, section_offset = line_info + # Create local references to oft-used attributes and methods. + full_width = self.size.width + gutter_width = self.gutter_width + text_width = full_width - gutter_width + highlights = self._highlights if self._highlights and self.theme else None + line_count = self.size.height + if line_count < 1: + # We have no visible lines. + return PreRenderState([]) + + line_number_start = self.line_number_start + scroll_x, scroll_y = self.scroll_offset + selection = self.selection + soft_wrap = self.soft_wrap + wrapped_document = self.wrapped_document - line = self.get_line(line_index) - line_character_count = len(line) - line.tab_size = self.indent_width - line.set_length(line_character_count + 1) # space at end for cursor - virtual_width, _virtual_height = self.virtual_size + # Local references to the wrapped document attribute and methods + get_sections = wrapped_document.get_sections + line_index_to_offsets = wrapped_document._line_index_to_offsets + offset_to_line_info = wrapped_document._offset_to_line_info + wrap_offsets = wrapped_document._wrap_offsets + wrapped_height = wrapped_document.height # Note: slowish property. - selection = self.selection - start, end = selection - cursor_row, cursor_column = end + visible_line_range = range(line_count) + if self.show_line_numbers: + gutter_width_no_margin = gutter_width - 2 + blank_gutter_text = " " * (gutter_width_no_margin + 2) + else: + gutter_width = 0 + blank_gutter_text = "" selection_top, selection_bottom = sorted(selection) selection_top_row, selection_top_column = selection_top selection_bottom_row, selection_bottom_column = selection_bottom + if selection.is_empty: + selection_line_range = EMPTY_RANGE + else: + selection_line_range = range(selection_top_row, selection_bottom_row + 1) + + doc_y_to_render_y_table = build_doc_y_to_render_y_table() + cursor_row, cursor_column = selection.end + cursor_char_x, cursor_y = doc_pos_to_xy(*selection.end) + cursor_x = cursor_char_x + if self._matching_bracket_location is not None: + bracket_char_x, bracket_y = doc_pos_to_xy(*self._matching_bracket_location) + bracket_x = bracket_char_x + else: + bracket_char_x = bracket_x = bracket_y = -1 + if not soft_wrap: + cursor_char_x -= scroll_x + bracket_x -= scroll_x + bracket_char_x -= scroll_x + + cursor_line_text: str | None = None + text_repr_strings: dict[int, TextReprString] = {} + rendered_lines: list[PreRenderLine] = [] + for y in visible_line_range: + if y not in text_repr_strings: + add_pre_renders(y) + + if bracket_char_x >= 0: + try: + bracket_x = text_repr_strings[bracket_y].cell_offset(bracket_char_x) + except IndexError: + # This can occur when the cursor is beyond RHS. + bracket_x = bracket_char_x = -1 + else: + bracket_x = -1 + cursor_width = 1 + if cursor_line_text is not None: # Should always be true. + cursor_char_x = cursor_line_text.adjust_index_for_tabs(cursor_char_x) + cursor_x = cursor_line_text.cell_offset(cursor_char_x) + if cursor_char_x < len(cursor_line_text): + cursor_width = cursor_line_text.cell_width(cursor_char_x) + + return PreRenderState( + lines=rendered_lines, + cursor=(self.cursor_is_on, cursor_x, cursor_y, cursor_width, cursor_char_x), + size=self.size, + gutter_width=gutter_width, + bracket=(bracket_x, bracket_y, bracket_char_x), + ) + + def render_line(self, y: int) -> Strip: + """Render a single line of the TextArea. Called by Textual. - cursor_line_style = theme.cursor_line_style if theme else None - if cursor_line_style and cursor_row == line_index: - line.stylize(cursor_line_style) + Args: + y: Y Coordinate of line relative to the widget region. - # Selection styling - if start != end and selection_top_row <= line_index <= selection_bottom_row: - # If this row intersects with the selection range + Returns: + A rendered line. + """ + state = self._old_render_state + if y >= len(state.lines): + return Strip.blank(self.size.width) + if y + self.scroll_offset.y >= self.wrapped_document.height: + return Strip.blank(self.size.width) + + # Get the text for this physical line. + pre_render = state.lines[y] + text = pre_render.text + rich_line = Text(text + " ", end="") + if theme := self._theme: + theme.apply_css(self) + if cursor_highlighted := pre_render.cursor_highlighted: + cursor_line_style = theme.cursor_line_style if theme else None + rich_line.stylize(cursor_line_style) + + # If this line is part of the selection, add selection styling. + if sel_range := pre_render.select_range: selection_style = theme.selection_style if theme else None - cursor_row, _ = end - if selection_style: - if line_character_count == 0 and line_index != cursor_row: - # A simple highlight to show empty lines are included in the selection - line = Text("β–Œ", end="", style=Style(color=selection_style.bgcolor)) + if selection_style is not None: + if sel_range.step < 0: + rich_line = Text( + "β–Œ", end="", style=Style(color=selection_style.bgcolor) + ) else: - if line_index == selection_top_row == selection_bottom_row: - # Selection within a single line - line.stylize( - selection_style, - start=selection_top_column, - end=selection_bottom_column, - ) - else: - # Selection spanning multiple lines - if line_index == selection_top_row: - line.stylize( - selection_style, - start=selection_top_column, - end=line_character_count, - ) - elif line_index == selection_bottom_row: - line.stylize(selection_style, end=selection_bottom_column) - else: - line.stylize(selection_style, end=line_character_count) + rich_line.stylize( + selection_style, start=sel_range.start, end=sel_range.stop + ) - highlights = self._highlights - if highlights and theme: - line_bytes = _utf8_encode(line.plain) - byte_to_codepoint = build_byte_to_codepoint_dict(line_bytes) + # Add syntax highlighting. + if (line_highlights := pre_render.syntax) and theme: get_highlight_from_theme = theme.syntax_styles.get - line_highlights = highlights[line_index] for highlight_start, highlight_end, highlight_name in line_highlights: node_style = get_highlight_from_theme(highlight_name) if node_style is not None: - line.stylize( - node_style, - byte_to_codepoint.get(highlight_start, 0), - byte_to_codepoint.get(highlight_end) if highlight_end else None, - ) + rich_line.stylize(node_style, highlight_start, highlight_end) - # Highlight the cursor - matching_bracket = self._matching_bracket_location - match_cursor_bracket = self.match_cursor_bracket - draw_matched_brackets = ( - match_cursor_bracket and matching_bracket is not None and start == end - ) + # Highlight the cursor, taking care when it is on a bracket. + draw_matched_brackets = False + if self._matching_bracket_location is not None: + if self.match_cursor_bracket: + draw_matched_brackets = self.selection.is_empty - if cursor_row == line_index: - draw_cursor = ( - self.has_focus - and not self.cursor_blink - or (self.cursor_blink and self._cursor_visible) - ) + cursor_is_on, cell_x, cell_y, _, char_x = state.cursor + if cell_y == y: if draw_matched_brackets: matching_bracket_style = theme.bracket_matching_style if theme else None if matching_bracket_style: - line.stylize( - matching_bracket_style, - cursor_column, - cursor_column + 1, - ) - - if draw_cursor: + rich_line.stylize(matching_bracket_style, char_x, char_x + 1) + if cursor_is_on: cursor_style = theme.cursor_style if theme else None if cursor_style: - line.stylize(cursor_style, cursor_column, cursor_column + 1) - - # Highlight the partner opening/closing bracket. - if draw_matched_brackets: - # mypy doesn't know matching bracket is guaranteed to be non-None - assert matching_bracket is not None - bracket_match_row, bracket_match_column = matching_bracket - if theme and bracket_match_row == line_index: + width = text.logical_character_width(char_x) + rich_line.stylize(cursor_style, char_x, char_x + width) + + # Add styling for a matching bracket. + _, bracket_cell_y, bracket_char_x = state.bracket + if theme and draw_matched_brackets: + if bracket_cell_y == y and bracket_char_x >= 0: matching_bracket_style = theme.bracket_matching_style if matching_bracket_style: - line.stylize( - matching_bracket_style, - bracket_match_column, - bracket_match_column + 1, + rich_line.stylize( + matching_bracket_style, bracket_char_x, bracket_char_x + 1 ) - # Build the gutter text for this line + # Add gutter text. gutter_width = self.gutter_width + gutter = Text("", end="") + gutter_style = theme.gutter_style if self.show_line_numbers: - if cursor_row == line_index: + if cursor_highlighted: gutter_style = theme.cursor_line_gutter_style - else: - gutter_style = theme.gutter_style + gutter = Text(pre_render.gutter_text, style=gutter_style or "", end="") - gutter_width_no_margin = gutter_width - 2 - gutter_content = ( - str(line_index + self.line_number_start) if section_offset == 0 else "" - ) - gutter = Text( - f"{gutter_content:>{gutter_width_no_margin}} ", - style=gutter_style or "", - end="", - ) - else: - gutter = Text("", end="") - - # TODO: Lets not apply the division each time through render_line. - # We should cache sections with the edit counts. - wrap_offsets = wrapped_document.get_offsets(line_index) - if wrap_offsets: - sections = line.divide(wrap_offsets) # TODO cache result with edit count - line = sections[section_offset] - line_tab_widths = wrapped_document.get_tab_widths(line_index) - line.end = "" - - # Get the widths of the tabs corresponding only to the section of the - # line that is currently being rendered. We don't care about tabs in - # other sections of the same line. - - # Count the tabs before this section. - tabs_before = 0 - for section_index in range(section_offset): - tabs_before += sections[section_index].plain.count("\t") - - # Count the tabs in this section. - tabs_within = line.plain.count("\t") - section_tab_widths = line_tab_widths[ - tabs_before : tabs_before + tabs_within - ] - line = expand_text_tabs_from_widths(line, section_tab_widths) - else: - line.expand_tabs(self.indent_width) - - base_width = ( - self.scrollable_content_region.size.width - if self.soft_wrap - else max(virtual_width, self.region.size.width) - ) - target_width = base_width - self.gutter_width + # Create strips for the gutter and line text. + base_width = self.scrollable_content_region.size.width + if not self.soft_wrap: + base_width = max(self.virtual_size.width, self.region.size.width) + target_width = base_width - gutter_width console = self.app.console gutter_segments = console.render(gutter) - text_segments = list( - console.render(line, console.options.update_width(target_width)) + console.render(rich_line, console.options.update_width(target_width)) ) - gutter_strip = Strip(gutter_segments, cell_length=gutter_width) text_strip = Strip(text_segments) - # Crop the line to show only the visible part (some may be scrolled out of view) - if not self.soft_wrap: - text_strip = text_strip.crop(scroll_x, scroll_x + virtual_width) - - # Stylize the line the cursor is currently on. - if cursor_row == line_index: + # Pad the line using the cursor line or base style. + if cursor_highlighted: line_style = cursor_line_style else: line_style = theme.base_style if theme else None - text_strip = text_strip.extend_cell_length(target_width, line_style) - strip = Strip.join([gutter_strip, text_strip]).simplify() + strip = Strip.join([gutter_strip, text_strip]).simplify() return strip.apply_style( theme.base_style if theme and theme.base_style is not None @@ -1373,7 +2159,7 @@ def edit(self, edit: Edit) -> EditResult: self._refresh_size() edit.after(self) - self._build_highlight_map() + self._handle_change_affecting_highlighting() self.post_message(self.Changed(self)) return result @@ -1436,7 +2222,7 @@ def _undo_batch(self, edits: Sequence[Edit]) -> None: self._refresh_size() for edit in reversed(edits): edit.after(self) - self._build_highlight_map() + self._handle_change_affecting_highlighting() self.post_message(self.Changed(self)) def _redo_batch(self, edits: Sequence[Edit]) -> None: @@ -1484,7 +2270,7 @@ def _redo_batch(self, edits: Sequence[Edit]) -> None: self._refresh_size() for edit in edits: edit.after(self) - self._build_highlight_map() + self._handle_change_affecting_highlighting() self.post_message(self.Changed(self)) async def _on_key(self, event: events.Key) -> None: @@ -1504,7 +2290,7 @@ async def _on_key(self, event: events.Key) -> None: self.screen.focus_next() return if self.indent_type == "tabs": - insert_values["tab"] = "\t" + insert_values["tab"] = TAB else: insert_values["tab"] = " " * self._find_columns_to_next_tab_stop() @@ -1593,12 +2379,12 @@ def _toggle_cursor_blink_visible(self) -> None: """Toggle visibility of the cursor for the purposes of 'cursor blink'.""" self._cursor_visible = not self._cursor_visible _, cursor_y = self._cursor_offset - self.refresh_lines(cursor_y) + self._trigger_repaint() def _watch__cursor_visible(self) -> None: """When the cursor visibility is toggled, ensure the row is refreshed.""" _, cursor_y = self._cursor_offset - self.refresh_lines(cursor_y) + self._trigger_repaint() def _restart_blink(self) -> None: """Reset the cursor blink timer.""" @@ -1611,6 +2397,11 @@ def _pause_blink(self, visible: bool = True) -> None: self._cursor_visible = visible self.blink_timer.pause() + @property + def cursor_is_on(self) -> bool: + """True if the cursor currently visible.""" + return self.has_focus and (self._cursor_visible or not self.cursor_blink) + async def _on_mouse_down(self, event: events.MouseDown) -> None: """Update the cursor position, and begin a selection using the mouse.""" target = self.get_target_document_location(event) @@ -1742,6 +2533,7 @@ def move_cursor( self.scroll_cursor_visible(center) self.history.checkpoint() + self._trigger_repaint() def move_cursor_relative( self, @@ -2368,41 +3160,157 @@ def action_delete_word_right(self) -> None: self._delete_via_keyboard(end, to_location) +class IndexMapper: + """An infinite list-like mapping from one index to another.""" + + def __init__(self, base_map: MutableSequence[int]): + self._base_map = base_map or [0] + + def __getitem__(self, index: int | None) -> int | None: + if index is None: + return None + try: + return self._base_map[index] + except IndexError: + return self._base_map[-1] + index - len(self._base_map) + 1 + + +class _IdentityIndexMapper(IndexMapper): + """A `Mapper` that maps 0->0, 1->1, ...""" + + def __init__(self): + pass + + def __getitem__(self, index: int | None) -> int | None: + return index + + @lru_cache(maxsize=128) -def build_byte_to_codepoint_dict(data: bytes) -> dict[int, int]: - """Build a mapping of utf-8 byte offsets to codepoint offsets for the given data. +def build_byte_to_tab_expanded_string_table(text: str, tab_size: int) -> Mapper: + """Build a mapping of utf-8 byte offsets to TAB-expanded character positions. Args: - data: utf-8 bytes. + text: The string to map. + tab_size: The size setting to use for TAB expansion. Returns: - A `dict[int, int]` mapping byte indices to codepoint indices within `data`. + A list-like object mapping byte index to character index. """ - byte_to_codepoint: dict[int, int] = {} - current_byte_offset = 0 - code_point_offset = 0 - - while current_byte_offset < len(data): - byte_to_codepoint[current_byte_offset] = code_point_offset - first_byte = data[current_byte_offset] - - # Single-byte character - if (first_byte & 0b10000000) == 0: - current_byte_offset += 1 - # 2-byte character - elif (first_byte & 0b11100000) == 0b11000000: - current_byte_offset += 2 - # 3-byte character - elif (first_byte & 0b11110000) == 0b11100000: - current_byte_offset += 3 - # 4-byte character - elif (first_byte & 0b11111000) == 0b11110000: - current_byte_offset += 4 + if not text: + return identity_index_mapper + + data = text.encode("utf-8") + if len(data) == len(text) and TAB not in text: + return identity_index_mapper + + offsets: MutableSequence[int] = array("L") + char_offset = 0 + next_tabstop = 0 + for c in text: + offsets.append(char_offset) + if c == TAB: + char_offset = next_tabstop else: - raise ValueError(f"Invalid UTF-8 byte: {first_byte}") + ord_c = ord(c) + if ord_c >= 0x80: + offsets.append(char_offset) + if ord_c >= 0x800: + offsets.append(char_offset) + if ord_c >= 0x10000: + offsets.append(char_offset) + char_offset += 1 + + if char_offset >= next_tabstop: + next_tabstop += tab_size + + return IndexMapper(offsets) + + +@contextmanager +def temporary_query_point_range( + query: Query, + start_point: tuple[int, int] | None, + end_point: tuple[int, int] | None, +) -> ContextManager[None]: + """Temporarily change the start and/or end point for a tree-sitter Query. + + Args: + query: The tree-sitter Query. + start_point: The (row, column byte) to start the query at. + end_point: The (row, column byte) to end the query at. + """ + # Note: Although not documented for the tree-sitter Python API, an + # end-point of (0, 0) means 'end of document'. + default_point_range = [(0, 0), (0, 0)] + + point_range = list(default_point_range) + if start_point is not None: + point_range[0] = start_point + if end_point is not None: + point_range[1] = end_point + query.set_point_range(point_range) + try: + yield None + finally: + query.set_point_range(default_point_range) + + +def build_difference_region( + line_index: int, + a: Sequence, + b: Sequence, + x_offset: int = 0, +) -> LineRegion: + """Compare 2 sequences to create a line region covering the differences. + + The caller should only use this if a nd b are known to differ. + """ + if a is None: + return LineRegion(x_offset, line_index, len(b)) + elif b is None: + return LineRegion(x_offset, line_index, len(a)) + + start = 0 + for start, (ca, cb) in enumerate(zip(a, b)): + if ca != cb: + break + end = max(len(a), len(b)) + return LineRegion(start + x_offset, line_index, end - start + 1) + + +def intersect_ranges(range_a, range_b) -> list[range]: + if range_a.step < 0: + range_b = range(0) + if range_b.step < 0: + range_b = range(0) + if range_a.start > range_b.start: + range_a, range_b = range_b, range_a + elif range_a.start == range_b.start: + if range_a.stop > range_b.stop: + range_a, range_b = range_b, range_a + overlap = range_a.stop - range_b.start + if overlap <= 0: + # Ranges do not overlap + before = range_a + common = [] + after = range_b + else: + before = range(range_a.start, range_a.stop - overlap) + common = range(range_a.stop - overlap, range_a.stop) + after = range(range_b.start + overlap, range_b.stop) + return before, common, after + + +def character_cell_size(char: str) -> int: + """Calculate the cell size for a character. + + This is athing wrapper around get_character_cell_size, which treats the TAB + character as width == 1. + """ + if char == TAB: + return 1 + else: + return get_character_cell_size(char) - code_point_offset += 1 - # Mapping for the end of the string - byte_to_codepoint[current_byte_offset] = code_point_offset - return byte_to_codepoint +identity_index_mapper = _IdentityIndexMapper() diff --git a/tests/document/test_document_delete.py b/tests/document/test_document_delete.py index d00fa686c9..0707062f7f 100644 --- a/tests/document/test_document_delete.py +++ b/tests/document/test_document_delete.py @@ -16,7 +16,11 @@ def document(): def test_delete_single_character(document): replace_result = document.replace_range((0, 0), (0, 1), "") - assert replace_result == EditResult(end_location=(0, 0), replaced_text="I") + assert replace_result == EditResult( + end_location=(0, 0), + replaced_text="I", + alt_dirty_line=(0, range(0, 16)), + ) assert document.lines == [ " must not fear.", "Fear is the mind-killer.", @@ -28,7 +32,11 @@ def test_delete_single_character(document): def test_delete_single_newline(document): """Testing deleting newline from right to left""" replace_result = document.replace_range((1, 0), (0, 16), "") - assert replace_result == EditResult(end_location=(0, 16), replaced_text="\n") + assert replace_result == EditResult( + end_location=(0, 16), + replaced_text="\n", + dirty_lines=range(0, 3), + ) assert document.lines == [ "I must not fear.Fear is the mind-killer.", "I forgot the rest of the quote.", @@ -44,6 +52,7 @@ def test_delete_near_end_of_document(document): replaced_text="Fear is the mind-killer.\n" "I forgot the rest of the quote.\n" "Sorry Will.", + dirty_lines=range(1, 2), ) assert document.lines == [ "I must not fear.", @@ -56,6 +65,7 @@ def test_delete_clearing_the_document(document): assert replace_result == EditResult( end_location=(0, 0), replaced_text=TEXT, + dirty_lines=range(0, 1), ) assert document.lines == [""] @@ -65,6 +75,7 @@ def test_delete_multiple_characters_on_one_line(document): assert replace_result == EditResult( end_location=(0, 2), replaced_text="must ", + alt_dirty_line=(0, range(2, 16)), ) assert document.lines == [ "I not fear.", @@ -80,6 +91,7 @@ def test_delete_multiple_lines_partially_spanned(document): assert replace_result == EditResult( end_location=(0, 2), replaced_text="must not fear.\nFear is the mind-killer.\nI ", + dirty_lines=range(0, 2), ) assert document.lines == [ "I forgot the rest of the quote.", @@ -93,6 +105,7 @@ def test_delete_end_of_line(document): assert replace_result == EditResult( end_location=(0, 16), replaced_text="\n", + dirty_lines=range(0, 3), ) assert document.lines == [ "I must not fear.Fear is the mind-killer.", @@ -107,6 +120,7 @@ def test_delete_single_line_excluding_newline(document): assert replace_result == EditResult( end_location=(2, 0), replaced_text="I forgot the rest of the quote.", + alt_dirty_line=(2, range(0, 31)), ) assert document.lines == [ "I must not fear.", @@ -122,6 +136,7 @@ def test_delete_single_line_including_newline(document): assert replace_result == EditResult( end_location=(2, 0), replaced_text="I forgot the rest of the quote.\n", + dirty_lines=range(2, 3), ) assert document.lines == [ "I must not fear.", @@ -139,7 +154,11 @@ def test_delete_single_line_including_newline(document): def test_delete_end_of_file_newline(): document = Document(TEXT_NEWLINE_EOF) replace_result = document.replace_range((2, 0), (1, 24), "") - assert replace_result == EditResult(end_location=(1, 24), replaced_text="\n") + assert replace_result == EditResult( + end_location=(1, 24), + replaced_text="\n", + dirty_lines=range(1, 2), + ) assert document.lines == [ "I must not fear.", "Fear is the mind-killer.", diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press0].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press0].svg new file mode 100644 index 0000000000..b2e4ffdaa9 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press0].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defa_function_with_a_long_name(lonβ–Ž +β–Š2  if long_argument_name == 42:   β–Ž +β–Š3  print('I think you may haveβ–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one whoβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press1].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press1].svg new file mode 100644 index 0000000000..37a31849fd --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press1].svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  efa_function_with_a_long_name(longβ–Ž +β–Š2  if long_argument_name == 42:    β–Ž +β–Š3  print('I think you may have β–Ž +β–Š4  else:                           β–Ž +β–Š5  print('You need the one who β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Œβ–‰β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press2].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press2].svg new file mode 100644 index 0000000000..a0888d998f --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press2].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  a_long_name(long_argument_name):β–Ž +β–Š2  _name == 42:                       β–Ž +β–Š3  nk you may have figured it out!')  β–Ž +β–Š4  β–Ž +β–Š5  eed the one who is to come after meβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Žβ–‹β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press3].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press3].svg new file mode 100644 index 0000000000..4e07cc22c6 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press3].svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  ng_name(long_argument_name):       β–Ž +β–Š2  e == 42:                           β–Ž +β–Š3  ou may have figured it out!')      β–Ž +β–Š4  β–Ž +β–Š5  the one who is to come after me!')β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–‹β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press4].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press4].svg new file mode 100644 index 0000000000..aa6c936a26 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling[press4].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  ng_name(long_argument_name):       β–Ž +β–Š2  e == 42:                           β–Ž +β–Š3  ou may have figured it out!')      β–Ž +β–Š4  β–Ž +β–Š5  the one who is to come after me!')β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–‹β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press0].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press0].svg new file mode 100644 index 0000000000..1d9f07d9cb --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press0].svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  n_with_a_long_name(long_argument_naβ–Ž +β–Š2  rgument_name == 42:                β–Ž +β–Š3  (β–Ž +β–Š4  'You have figured it out!')β–Ž +β–Š5  β–Ž +β–Š6  (                                  β–Ž +β–Š7  You need the one who is to come aftβ–Ž +β–Šβ–β–Žβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press1].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press1].svg new file mode 100644 index 0000000000..233ea07482 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press1].svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  _with_a_long_name(long_argument_namβ–Ž +β–Š2  gument_name == 42:                 β–Ž +β–Š3  β–Ž +β–Š4  'You have figured it out!')#β–Ž +β–Š5  β–Ž +β–Š6  β–Ž +β–Š7  ou need the one who is to come afteβ–Ž +β–Šβ–Šβ–‰β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press2].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press2].svg new file mode 100644 index 0000000000..a77ea1205f --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_horizontal_scrolling_cursor_matching[press2].svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defa_function_with_a_long_name(lonβ–Ž +β–Š2  if long_argument_name == 42:   β–Ž +β–Š3  print(                     β–Ž +β–Š4  'You have figureβ–Ž +β–Š5  else:                          β–Ž +β–Š6  print(β–Ž +β–Š7  'You need the one who iβ–Ž +β–Šβ–β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[bash].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[bash].svg index fac1d695c7..13a54e6f87 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[bash].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[bash].svg @@ -19,457 +19,457 @@ font-weight: 700; } - .terminal-1868575305-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1868575305-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1868575305-r1 { fill: #121212 } -.terminal-1868575305-r2 { fill: #0178d4 } -.terminal-1868575305-r3 { fill: #c5c8c6 } -.terminal-1868575305-r4 { fill: #c2c2bf } -.terminal-1868575305-r5 { fill: #272822 } -.terminal-1868575305-r6 { fill: #75715e } -.terminal-1868575305-r7 { fill: #f8f8f2 } -.terminal-1868575305-r8 { fill: #90908a } -.terminal-1868575305-r9 { fill: #e6db74 } -.terminal-1868575305-r10 { fill: #a6e22e } -.terminal-1868575305-r11 { fill: #f92672 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #75715e } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #e6db74 } +.terminal-r10 { fill: #a6e22e } +.terminal-r11 { fill: #f92672 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š  1  #!/bin/bashβ–Ž -β–Š  2  β–Ž -β–Š  3  # Variablesβ–Ž -β–Š  4  name="John"β–Ž -β–Š  5  age=30                                                                 β–Ž -β–Š  6  is_student=true                                                        β–Ž -β–Š  7  β–Ž -β–Š  8  # Printing variablesβ–Ž -β–Š  9  echo"Hello, $name! You are $age years old."β–Ž -β–Š 10  β–Ž -β–Š 11  # Conditional statementsβ–Ž -β–Š 12  if [[ $age -ge 18 && $is_student == true ]]; thenβ–Ž -β–Š 13  echo"You are an adult student."β–Ž -β–Š 14  elif [[ $age -ge 18 ]]; thenβ–Ž -β–Š 15  echo"You are an adult."β–Ž -β–Š 16  elseβ–Ž -β–Š 17  echo"You are a minor."β–Ž -β–Š 18  fiβ–Ž -β–Š 19  β–Ž -β–Š 20  # Arraysβ–Ž -β–Š 21  numbers=(1 2 3 4 5)                                                    β–Ž -β–Š 22  echo"Numbers: ${numbers[@]}"β–Ž -β–Š 23  β–Ž -β–Š 24  # Loopsβ–Ž -β–Š 25  for num in"${numbers[@]}"doβ–Ž -β–Š 26  echo"Number: $num"β–Ž -β–Š 27  doneβ–Ž -β–Š 28  β–Ž -β–Š 29  # Functionsβ–Ž -β–Š 30  greet() {                                                              β–Ž -β–Š 31    local name=$1                                                        β–Ž -β–Š 32  echo"Hello, $name!"β–Ž -β–Š 33  }                                                                      β–Ž -β–Š 34  greet"Alice"β–Ž -β–Š 35  β–Ž -β–Š 36  # Command substitutionβ–Ž -β–Š 37  current_date=$(date +%Y-%m-%d)                                         β–Ž -β–Š 38  echo"Current date: $current_date"β–Ž -β–Š 39  β–Ž -β–Š 40  # File operationsβ–Ž -β–Š 41  touch file.txt                                                         β–Ž -β–Š 42  echo"Some content" > file.txt                                         β–Ž -β–Š 43  cat file.txt                                                           β–Ž -β–Š 44  β–Ž -β–Š 45  # Conditionals with file checksβ–Ž -β–Š 46  if [[ -f file.txt ]]; thenβ–Ž -β–Š 47  echo"file.txt exists."β–Ž -β–Š 48  elseβ–Ž -β–Š 49  echo"file.txt does not exist."β–Ž -β–Š 50  fiβ–Ž -β–Š 51  β–Ž -β–Š 52  # Case statementβ–Ž -β–Š 53  case $age inβ–Ž -β–Š 54    18)                                                                  β–Ž -β–Š 55  echo"You are 18 years old."β–Ž -β–Š 56      ;;                                                                 β–Ž -β–Š 57    30)                                                                  β–Ž -β–Š 58  echo"You are 30 years old."β–Ž -β–Š 59      ;;                                                                 β–Ž -β–Š 60    *)                                                                   β–Ž -β–Š 61  echo"You are neither 18 nor 30 years old."β–Ž -β–Š 62      ;;                                                                 β–Ž -β–Š 63  esacβ–Ž -β–Š 64  β–Ž -β–Š 65  # While loopβ–Ž -β–Š 66  counter=0                                                              β–Ž -β–Š 67  while [[ $counter -lt 5 ]]; doβ–Ž -β–Š 68  echo"Counter: $counter"β–Ž -β–Š 69    ((counter++))                                                        β–Ž -β–Š 70  doneβ–Ž -β–Š 71  β–Ž -β–Š 72  # Until loopβ–Ž -β–Š 73  until [[ $counter -eq 0 ]]; doβ–Ž -β–Š 74  echo"Counter: $counter"β–Ž -β–Š 75    ((counter--))                                                        β–Ž -β–Š 76  doneβ–Ž -β–Š 77  β–Ž -β–Š 78  # Heredocβ–Ž -β–Š 79  cat << EOFβ–Ž -β–Š 80  This is a heredoc. β–Ž -β–Š 81  It allows you to write multiple lines of text. β–Ž -β–Š 82  EOF β–Ž -β–Š 83  β–Ž -β–Š 84  # Redirectionβ–Ž -β–Š 85  ls > file_list.txt                                                     β–Ž -β–Š 86  grep"file" file_list.txt > filtered_list.txt                          β–Ž -β–Š 87  β–Ž -β–Š 88  # Pipesβ–Ž -β–Š 89  cat file_list.txt | wc -l                                              β–Ž -β–Š 90  β–Ž -β–Š 91  # Arithmetic operationsβ–Ž -β–Š 92  result=$((10 + 5))                                                     β–Ž -β–Š 93  echo"Result: $result"β–Ž -β–Š 94  β–Ž -β–Š 95  # Exporting variablesβ–Ž -β–Š 96  export DB_PASSWORD="secret"β–Ž -β–Š 97  β–Ž -β–Š 98  # Sourcing external filesβ–Ž -β–Š 99  source config.sh                                                       β–Ž -β–Š100  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š  1  #!/bin/bashβ–Ž +β–Š  2  β–Ž +β–Š  3  # Variablesβ–Ž +β–Š  4  name="John"β–Ž +β–Š  5  age=30                                                                 β–Ž +β–Š  6  is_student=true                                                        β–Ž +β–Š  7  β–Ž +β–Š  8  # Printing variablesβ–Ž +β–Š  9  echo"Hello, $name! You are $age years old."β–Ž +β–Š 10  β–Ž +β–Š 11  # Conditional statementsβ–Ž +β–Š 12  if [[ $age -ge 18 && $is_student == true ]]; thenβ–Ž +β–Š 13  echo"You are an adult student."β–Ž +β–Š 14  elif [[ $age -ge 18 ]]; thenβ–Ž +β–Š 15  echo"You are an adult."β–Ž +β–Š 16  elseβ–Ž +β–Š 17  echo"You are a minor."β–Ž +β–Š 18  fiβ–Ž +β–Š 19  β–Ž +β–Š 20  # Arraysβ–Ž +β–Š 21  numbers=(1 2 3 4 5)                                                    β–Ž +β–Š 22  echo"Numbers: ${numbers[@]}"β–Ž +β–Š 23  β–Ž +β–Š 24  # Loopsβ–Ž +β–Š 25  for num in"${numbers[@]}"doβ–Ž +β–Š 26  echo"Number: $num"β–Ž +β–Š 27  doneβ–Ž +β–Š 28  β–Ž +β–Š 29  # Functionsβ–Ž +β–Š 30  greet() {                                                              β–Ž +β–Š 31    local name=$1                                                        β–Ž +β–Š 32  echo"Hello, $name!"β–Ž +β–Š 33  }                                                                      β–Ž +β–Š 34  greet"Alice"β–Ž +β–Š 35  β–Ž +β–Š 36  # Command substitutionβ–Ž +β–Š 37  current_date=$(date +%Y-%m-%d)                                         β–Ž +β–Š 38  echo"Current date: $current_date"β–Ž +β–Š 39  β–Ž +β–Š 40  # File operationsβ–Ž +β–Š 41  touch file.txt                                                         β–Ž +β–Š 42  echo"Some content" > file.txt                                         β–Ž +β–Š 43  cat file.txt                                                           β–Ž +β–Š 44  β–Ž +β–Š 45  # Conditionals with file checksβ–Ž +β–Š 46  if [[ -f file.txt ]]; thenβ–Ž +β–Š 47  echo"file.txt exists."β–Ž +β–Š 48  elseβ–Ž +β–Š 49  echo"file.txt does not exist."β–Ž +β–Š 50  fiβ–Ž +β–Š 51  β–Ž +β–Š 52  # Case statementβ–Ž +β–Š 53  case $age inβ–Ž +β–Š 54    18)                                                                  β–Ž +β–Š 55  echo"You are 18 years old."β–Ž +β–Š 56      ;;                                                                 β–Ž +β–Š 57    30)                                                                  β–Ž +β–Š 58  echo"You are 30 years old."β–Ž +β–Š 59      ;;                                                                 β–Ž +β–Š 60    *)                                                                   β–Ž +β–Š 61  echo"You are neither 18 nor 30 years old."β–Ž +β–Š 62      ;;                                                                 β–Ž +β–Š 63  esacβ–Ž +β–Š 64  β–Ž +β–Š 65  # While loopβ–Ž +β–Š 66  counter=0                                                              β–Ž +β–Š 67  while [[ $counter -lt 5 ]]; doβ–Ž +β–Š 68  echo"Counter: $counter"β–Ž +β–Š 69    ((counter++))                                                        β–Ž +β–Š 70  doneβ–Ž +β–Š 71  β–Ž +β–Š 72  # Until loopβ–Ž +β–Š 73  until [[ $counter -eq 0 ]]; doβ–Ž +β–Š 74  echo"Counter: $counter"β–Ž +β–Š 75    ((counter--))                                                        β–Ž +β–Š 76  doneβ–Ž +β–Š 77  β–Ž +β–Š 78  # Heredocβ–Ž +β–Š 79  cat << EOFβ–Ž +β–Š 80  This is a heredoc.β–Ž +β–Š 81  It allows you to write multiple lines of text.β–Ž +β–Š 82  EOF                                                                    β–Ž +β–Š 83  β–Ž +β–Š 84  # Redirectionβ–Ž +β–Š 85  ls > file_list.txt                                                     β–Ž +β–Š 86  grep"file" file_list.txt > filtered_list.txt                          β–Ž +β–Š 87  β–Ž +β–Š 88  # Pipesβ–Ž +β–Š 89  cat file_list.txt | wc -l                                              β–Ž +β–Š 90  β–Ž +β–Š 91  # Arithmetic operationsβ–Ž +β–Š 92  result=$((10 + 5))                                                     β–Ž +β–Š 93  echo"Result: $result"β–Ž +β–Š 94  β–Ž +β–Š 95  # Exporting variablesβ–Ž +β–Š 96  export DB_PASSWORD="secret"β–Ž +β–Š 97  β–Ž +β–Š 98  # Sourcing external filesβ–Ž +β–Š 99  source config.sh                                                       β–Ž +β–Š100  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg index 44ac0c0411..a49c38524e 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg @@ -19,330 +19,330 @@ font-weight: 700; } - .terminal-2526263208-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2526263208-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2526263208-r1 { fill: #121212 } -.terminal-2526263208-r2 { fill: #0178d4 } -.terminal-2526263208-r3 { fill: #c5c8c6 } -.terminal-2526263208-r4 { fill: #c2c2bf } -.terminal-2526263208-r5 { fill: #272822 } -.terminal-2526263208-r6 { fill: #75715e } -.terminal-2526263208-r7 { fill: #f8f8f2 } -.terminal-2526263208-r8 { fill: #90908a } -.terminal-2526263208-r9 { fill: #a6e22e } -.terminal-2526263208-r10 { fill: #ae81ff } -.terminal-2526263208-r11 { fill: #e6db74 } -.terminal-2526263208-r12 { fill: #f92672 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #75715e } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #a6e22e } +.terminal-r10 { fill: #ae81ff } +.terminal-r11 { fill: #e6db74 } +.terminal-r12 { fill: #f92672 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  /* This is a comment in CSS */β–Ž -β–Š 2  β–Ž -β–Š 3  /* Basic selectors and properties */β–Ž -β–Š 4  body {                                                                  β–Ž -β–Š 5  font-familyArialsans-serif;                                     β–Ž -β–Š 6  background-color#f4f4f4;                                          β–Ž -β–Š 7  margin0;                                                          β–Ž -β–Š 8  padding0;                                                         β–Ž -β–Š 9  }                                                                       β–Ž -β–Š10  β–Ž -β–Š11  /* Class and ID selectors */β–Ž -β–Š12  .header {                                                               β–Ž -β–Š13  background-color#333;                                             β–Ž -β–Š14  color#fff;                                                        β–Ž -β–Š15  padding10px0;                                                    β–Ž -β–Š16  text-aligncenter;                                                 β–Ž -β–Š17  }                                                                       β–Ž -β–Š18  β–Ž -β–Š19  #logo {                                                                 β–Ž -β–Š20  font-size24px;                                                    β–Ž -β–Š21  font-weightbold;                                                  β–Ž -β–Š22  }                                                                       β–Ž -β–Š23  β–Ž -β–Š24  /* Descendant and child selectors */β–Ž -β–Š25  .navul {                                                               β–Ž -β–Š26  list-style-typenone;                                              β–Ž -β–Š27  padding0;                                                         β–Ž -β–Š28  }                                                                       β–Ž -β–Š29  β–Ž -β–Š30  .nav > li {                                                             β–Ž -β–Š31  displayinline-block;                                              β–Ž -β–Š32  margin-right10px;                                                 β–Ž -β–Š33  }                                                                       β–Ž -β–Š34  β–Ž -β–Š35  /* Pseudo-classes */β–Ž -β–Š36  a:hover {                                                               β–Ž -β–Š37  text-decorationunderline;                                         β–Ž -β–Š38  }                                                                       β–Ž -β–Š39  β–Ž -β–Š40  input:focus {                                                           β–Ž -β–Š41  border-color#007BFF;                                              β–Ž -β–Š42  }                                                                       β–Ž -β–Š43  β–Ž -β–Š44  /* Media query */β–Ž -β–Š45  @media (max-width768px) {                                             β–Ž -β–Š46  body {                                                              β–Ž -β–Š47  font-size16px;                                                β–Ž -β–Š48      }                                                                   β–Ž -β–Š49  β–Ž -β–Š50      .header {                                                           β–Ž -β–Š51  padding5px0;                                                 β–Ž -β–Š52      }                                                                   β–Ž -β–Š53  }                                                                       β–Ž -β–Š54  β–Ž -β–Š55  /* Keyframes animation */β–Ž -β–Š56  @keyframes slideIn {                                                    β–Ž -β–Š57  from {                                                              β–Ž -β–Š58  transformtranslateX(-100%);                                   β–Ž -β–Š59      }                                                                   β–Ž -β–Š60  to {                                                                β–Ž -β–Š61  transformtranslateX(0);                                       β–Ž -β–Š62      }                                                                   β–Ž -β–Š63  }                                                                       β–Ž -β–Š64  β–Ž -β–Š65  .slide-in-element {                                                     β–Ž -β–Š66  animationslideIn0.5sforwards;                                   β–Ž -β–Š67  }                                                                       β–Ž -β–Š68  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  /* This is a comment in CSS */β–Ž +β–Š 2  β–Ž +β–Š 3  /* Basic selectors and properties */β–Ž +β–Š 4  body {                                                                  β–Ž +β–Š 5  font-familyArialsans-serif;                                     β–Ž +β–Š 6  background-color: #f4f4f4;                                          β–Ž +β–Š 7  margin0;                                                          β–Ž +β–Š 8  padding0;                                                         β–Ž +β–Š 9  }                                                                       β–Ž +β–Š10  β–Ž +β–Š11  /* Class and ID selectors */β–Ž +β–Š12  .header {                                                               β–Ž +β–Š13  background-color: #333;                                             β–Ž +β–Š14  color: #fff;                                                        β–Ž +β–Š15  padding10px0;                                                    β–Ž +β–Š16  text-aligncenter;                                                 β–Ž +β–Š17  }                                                                       β–Ž +β–Š18  β–Ž +β–Š19  #logo {                                                                 β–Ž +β–Š20  font-size24px;                                                    β–Ž +β–Š21  font-weightbold;                                                  β–Ž +β–Š22  }                                                                       β–Ž +β–Š23  β–Ž +β–Š24  /* Descendant and child selectors */β–Ž +β–Š25  .navul {                                                               β–Ž +β–Š26  list-style-typenone;                                              β–Ž +β–Š27  padding0;                                                         β–Ž +β–Š28  }                                                                       β–Ž +β–Š29  β–Ž +β–Š30  .nav > li {                                                             β–Ž +β–Š31  displayinline-block;                                              β–Ž +β–Š32  margin-right10px;                                                 β–Ž +β–Š33  }                                                                       β–Ž +β–Š34  β–Ž +β–Š35  /* Pseudo-classes */β–Ž +β–Š36  a:hover {                                                               β–Ž +β–Š37  text-decorationunderline;                                         β–Ž +β–Š38  }                                                                       β–Ž +β–Š39  β–Ž +β–Š40  input:focus {                                                           β–Ž +β–Š41  border-color: #007BFF;                                              β–Ž +β–Š42  }                                                                       β–Ž +β–Š43  β–Ž +β–Š44  /* Media query */β–Ž +β–Š45  @media (max-width768px) {                                             β–Ž +β–Š46  body {                                                              β–Ž +β–Š47  font-size16px;                                                β–Ž +β–Š48      }                                                                   β–Ž +β–Š49  β–Ž +β–Š50      .header {                                                           β–Ž +β–Š51  padding5px0;                                                 β–Ž +β–Š52      }                                                                   β–Ž +β–Š53  }                                                                       β–Ž +β–Š54  β–Ž +β–Š55  /* Keyframes animation */β–Ž +β–Š56  @keyframes slideIn {                                                    β–Ž +β–Š57  from {                                                              β–Ž +β–Š58  transformtranslateX(-100%);                                   β–Ž +β–Š59      }                                                                   β–Ž +β–Š60  to {                                                                β–Ž +β–Š61  transformtranslateX(0);                                       β–Ž +β–Š62      }                                                                   β–Ž +β–Š63  }                                                                       β–Ž +β–Š64  β–Ž +β–Š65  .slide-in-element {                                                     β–Ž +β–Š66  animationslideIn0.5sforwards;                                   β–Ž +β–Š67  }                                                                       β–Ž +β–Š68  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[java].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[java].svg index 02656c7fec..bf808befa2 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[java].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[java].svg @@ -19,476 +19,476 @@ font-weight: 700; } - .terminal-1303054993-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1303054993-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1303054993-r1 { fill: #121212 } -.terminal-1303054993-r2 { fill: #0178d4 } -.terminal-1303054993-r3 { fill: #c5c8c6 } -.terminal-1303054993-r4 { fill: #c2c2bf } -.terminal-1303054993-r5 { fill: #272822 } -.terminal-1303054993-r6 { fill: #f92672 } -.terminal-1303054993-r7 { fill: #f8f8f2 } -.terminal-1303054993-r8 { fill: #a6e22e } -.terminal-1303054993-r9 { fill: #90908a } -.terminal-1303054993-r10 { fill: #75715e } -.terminal-1303054993-r11 { fill: #ae81ff } -.terminal-1303054993-r12 { fill: #e6db74 } -.terminal-1303054993-r13 { fill: #66d9ef;font-style: italic; } -.terminal-1303054993-r14 { fill: #003054 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #f92672 } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #75715e } +.terminal-r10 { fill: #a6e22e } +.terminal-r11 { fill: #ae81ff } +.terminal-r12 { fill: #e6db74 } +.terminal-r13 { fill: #66d9ef;font-style: italic; } +.terminal-r14 { fill: #003054 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š  1  importjava.util.ArrayList;                                            β–Ž -β–Š  2  importjava.util.HashMap;                                              β–Ž -β–Š  3  importjava.util.List;                                                 β–Ž -β–Š  4  importjava.util.Map;                                                  β–Ž -β–Š  5  β–Ž -β–Š  6  // Classes and interfacesβ–Ž -β–Š  7  interfaceShape {                                                      β–Ž -β–Š  8  double getArea();                                                  β–Ž -β–Š  9  }                                                                      β–Ž -β–Š 10  β–Ž -β–Š 11  classRectangleimplementsShape {                                     β–Ž -β–Š 12  privatedouble width;                                              β–Ž -β–Š 13  privatedouble height;                                             β–Ž -β–Š 14  β–Ž -β–Š 15  publicRectangle(double width, double height) {                    β–Ž -β–Š 16          this.width = width;                                            β–Ž -β–Š 17          this.height = height;                                          β–Ž -β–Š 18      }                                                                  β–Ž -β–Š 19  β–Ž -β–Š 20      @Override                                                          β–Ž -β–Š 21  publicdouble getArea() {                                          β–Ž -β–Š 22  return width * height;                                         β–Ž -β–Š 23      }                                                                  β–Ž -β–Š 24  }                                                                      β–Ž -β–Š 25  β–Ž -β–Š 26  // Enumsβ–Ž -β–Š 27  enumDaysOfWeek {                                                      β–Ž -β–Š 28      MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY     β–Ž -β–Š 29  }                                                                      β–Ž -β–Š 30  β–Ž -β–Š 31  publicclassMain {                                                    β–Ž -β–Š 32  // Constantsβ–Ž -β–Š 33  privatestaticfinaldouble PI = 3.14159;                          β–Ž -β–Š 34  β–Ž -β–Š 35  // Methodsβ–Ž -β–Š 36  publicstaticint sum(int a, int b) {                              β–Ž -β–Š 37  return a + b;                                                  β–Ž -β–Š 38      }                                                                  β–Ž -β–Š 39  β–Ž -β–Š 40  publicstaticvoid main(String[] args) {                           β–Ž -β–Š 41  // Variablesβ–Ž -β–Š 42  String name = "John";                                          β–Ž -β–Š 43  int age = 30;                                                  β–Ž -β–Š 44  boolean isStudent = true;                                      β–Ž -β–Š 45  β–Ž -β–Š 46  // Printing variablesβ–Ž -β–Š 47  System.out.println("Hello, " + name + "! You are " + age + " yeβ–Ž -β–Š 48  β–Ž -β–Š 49  // Conditional statementsβ–Ž -β–Š 50  if (age >= 18 && isStudent) {                                  β–Ž -β–Š 51  System.out.println("You are an adult student.");           β–Ž -β–Š 52          } elseif (age >= 18) {                                        β–Ž -β–Š 53  System.out.println("You are an adult.");                   β–Ž -β–Š 54          } else {                                                       β–Ž -β–Š 55  System.out.println("You are a minor.");                    β–Ž -β–Š 56          }                                                              β–Ž -β–Š 57  β–Ž -β–Š 58  // Arraysβ–Ž -β–Š 59  int[] numbers = {12345};                               β–Ž -β–Š 60  System.out.println("Numbers: " + Arrays.toString(numbers));    β–Ž -β–Š 61  β–Ž -β–Š 62  // Listsβ–Ž -β–Š 63  List<String> fruits = newArrayList<>();                       β–Ž -β–Š 64  fruits.add("apple");                                           β–Ž -β–Š 65  fruits.add("banana");                                          β–Ž -β–Š 66  fruits.add("orange");                                          β–Ž -β–Š 67  System.out.println("Fruits: " + fruits);                       β–Ž -β–Š 68  β–Ž -β–Š 69  // Loopsβ–Ž -β–Š 70  for (int num : numbers) {                                      β–Ž -β–Š 71  System.out.println("Number: " + num);                      β–Ž -β–Š 72          }                                                              β–Ž -β–Š 73  β–Ž -β–Š 74  // Hash mapsβ–Ž -β–Š 75  Map<StringInteger> scores = newHashMap<>();                 β–Ž -β–Š 76  scores.put("Alice"100);                                      β–Ž -β–Š 77  scores.put("Bob"80);                                         β–Ž -β–Š 78  System.out.println("Alice's score: " + scores.get("Alice"));   β–Ž -β–Š 79  β–Ž -β–Š 80  // Exception handlingβ–Ž -β–Š 81  try {                                                          β–Ž -β–Š 82  int result = 10 / 0;                                       β–Ž -β–Š 83          } catch (ArithmeticException e) {                              β–Ž -β–Š 84  System.out.println("Error: " + e.getMessage());            β–Ž -β–Š 85          }                                                              β–Ž -β–Š 86  β–Ž -β–Š 87  // Instantiating objectsβ–Ž -β–Š 88  Rectangle rect = newRectangle(1020);                        β–Ž -β–Š 89  System.out.println("Rectangle area: " + rect.getArea());       β–Ž -β–Š 90  β–Ž -β–Š 91  // Enumsβ–Ž -β–Š 92  DaysOfWeek today = DaysOfWeek.MONDAY;                          β–Ž -β–Š 93  System.out.println("Today is " + today);                       β–Ž -β–Š 94  β–Ž -β–Š 95  // Calling methodsβ–Ž -β–Š 96  int sum = sum(510);                                          β–Ž -β–Š 97  System.out.println("Sum: " + sum);                             β–Ž -β–Š 98  β–Ž -β–Š 99  // Ternary operatorβ–Ž -β–Š100  String message = age >= 18 ? "You are an adult." : "You are a mβ–Ž -β–Š101  System.out.println(message);                                   β–Ž -β–Š102      }                                                                  β–Ž -β–Š103  }                                                                      β–Ž -β–Š104  β–Ž -β–Šβ–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š  1  import java.util.ArrayList;                                            β–Ž +β–Š  2  import java.util.HashMap;                                              β–Ž +β–Š  3  import java.util.List;                                                 β–Ž +β–Š  4  import java.util.Map;                                                  β–Ž +β–Š  5  β–Ž +β–Š  6  // Classes and interfacesβ–Ž +β–Š  7  interfaceShape {                                                      β–Ž +β–Š  8  double getArea();                                                  β–Ž +β–Š  9  }                                                                      β–Ž +β–Š 10  β–Ž +β–Š 11  classRectangleimplementsShape {                                     β–Ž +β–Š 12  privatedouble width;                                              β–Ž +β–Š 13  privatedouble height;                                             β–Ž +β–Š 14  β–Ž +β–Š 15  publicRectangle(double width, double height) {                    β–Ž +β–Š 16          this.width = width;                                            β–Ž +β–Š 17          this.height = height;                                          β–Ž +β–Š 18      }                                                                  β–Ž +β–Š 19  β–Ž +β–Š 20      @Override                                                          β–Ž +β–Š 21  publicdouble getArea() {                                          β–Ž +β–Š 22  return width * height;                                         β–Ž +β–Š 23      }                                                                  β–Ž +β–Š 24  }                                                                      β–Ž +β–Š 25  β–Ž +β–Š 26  // Enumsβ–Ž +β–Š 27  enumDaysOfWeek {                                                      β–Ž +β–Š 28      MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY     β–Ž +β–Š 29  }                                                                      β–Ž +β–Š 30  β–Ž +β–Š 31  publicclassMain {                                                    β–Ž +β–Š 32  // Constantsβ–Ž +β–Š 33  privatestaticfinaldouble PI = 3.14159;                          β–Ž +β–Š 34  β–Ž +β–Š 35  // Methodsβ–Ž +β–Š 36  publicstaticint sum(int a, int b) {                              β–Ž +β–Š 37  return a + b;                                                  β–Ž +β–Š 38      }                                                                  β–Ž +β–Š 39  β–Ž +β–Š 40  publicstaticvoid main(String[] args) {                           β–Ž +β–Š 41  // Variablesβ–Ž +β–Š 42  String name = "John";                                          β–Ž +β–Š 43  int age = 30;                                                  β–Ž +β–Š 44  boolean isStudent = true;                                      β–Ž +β–Š 45  β–Ž +β–Š 46  // Printing variablesβ–Ž +β–Š 47  System.out.println("Hello, " + name + "! You are " + age + " yeβ–Ž +β–Š 48  β–Ž +β–Š 49  // Conditional statementsβ–Ž +β–Š 50  if (age >= 18 && isStudent) {                                  β–Ž +β–Š 51  System.out.println("You are an adult student.");           β–Ž +β–Š 52          } elseif (age >= 18) {                                        β–Ž +β–Š 53  System.out.println("You are an adult.");                   β–Ž +β–Š 54          } else {                                                       β–Ž +β–Š 55  System.out.println("You are a minor.");                    β–Ž +β–Š 56          }                                                              β–Ž +β–Š 57  β–Ž +β–Š 58  // Arraysβ–Ž +β–Š 59  int[] numbers = {12345};                               β–Ž +β–Š 60  System.out.println("Numbers: " + Arrays.toString(numbers));    β–Ž +β–Š 61  β–Ž +β–Š 62  // Listsβ–Ž +β–Š 63  List<String> fruits = newArrayList<>();                       β–Ž +β–Š 64          fruits.add("apple");                                           β–Ž +β–Š 65          fruits.add("banana");                                          β–Ž +β–Š 66          fruits.add("orange");                                          β–Ž +β–Š 67  System.out.println("Fruits: " + fruits);                       β–Ž +β–Š 68  β–Ž +β–Š 69  // Loopsβ–Ž +β–Š 70  for (int num : numbers) {                                      β–Ž +β–Š 71  System.out.println("Number: " + num);                      β–Ž +β–Š 72          }                                                              β–Ž +β–Š 73  β–Ž +β–Š 74  // Hash mapsβ–Ž +β–Š 75  Map<StringInteger> scores = newHashMap<>();                 β–Ž +β–Š 76          scores.put("Alice"100);                                      β–Ž +β–Š 77          scores.put("Bob"80);                                         β–Ž +β–Š 78  System.out.println("Alice's score: " + scores.get("Alice"));   β–Ž +β–Š 79  β–Ž +β–Š 80  // Exception handlingβ–Ž +β–Š 81  try {                                                          β–Ž +β–Š 82  int result = 10 / 0;                                       β–Ž +β–Š 83          } catch (ArithmeticException e) {                              β–Ž +β–Š 84  System.out.println("Error: " + e.getMessage());            β–Ž +β–Š 85          }                                                              β–Ž +β–Š 86  β–Ž +β–Š 87  // Instantiating objectsβ–Ž +β–Š 88  Rectangle rect = newRectangle(1020);                        β–Ž +β–Š 89  System.out.println("Rectangle area: " + rect.getArea());       β–Ž +β–Š 90  β–Ž +β–Š 91  // Enumsβ–Ž +β–Š 92  DaysOfWeek today = DaysOfWeek.MONDAY;                          β–Ž +β–Š 93  System.out.println("Today is " + today);                       β–Ž +β–Š 94  β–Ž +β–Š 95  // Calling methodsβ–Ž +β–Š 96  int sum = sum(510);                                          β–Ž +β–Š 97  System.out.println("Sum: " + sum);                             β–Ž +β–Š 98  β–Ž +β–Š 99  // Ternary operatorβ–Ž +β–Š100  String message = age >= 18 ? "You are an adult." : "You are a mβ–Ž +β–Š101  System.out.println(message);                                   β–Ž +β–Š102      }                                                                  β–Ž +β–Š103  }                                                                      β–Ž +β–Š104  β–Ž +β–Šβ–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg index 645ea326fa..210ce01926 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg @@ -19,371 +19,371 @@ font-weight: 700; } - .terminal-2506662657-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2506662657-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2506662657-r1 { fill: #121212 } -.terminal-2506662657-r2 { fill: #0178d4 } -.terminal-2506662657-r3 { fill: #c5c8c6 } -.terminal-2506662657-r4 { fill: #c2c2bf } -.terminal-2506662657-r5 { fill: #272822 } -.terminal-2506662657-r6 { fill: #75715e } -.terminal-2506662657-r7 { fill: #f8f8f2 } -.terminal-2506662657-r8 { fill: #90908a } -.terminal-2506662657-r9 { fill: #f92672 } -.terminal-2506662657-r10 { fill: #e6db74 } -.terminal-2506662657-r11 { fill: #ae81ff } -.terminal-2506662657-r12 { fill: #66d9ef;font-style: italic; } -.terminal-2506662657-r13 { fill: #a6e22e } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #75715e } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #f92672 } +.terminal-r10 { fill: #e6db74 } +.terminal-r11 { fill: #ae81ff } +.terminal-r12 { fill: #66d9ef;font-style: italic; } +.terminal-r13 { fill: #a6e22e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  // Variable declarationsβ–Ž -β–Š 2  const name = "John";                                                    β–Ž -β–Š 3  let age = 30;                                                           β–Ž -β–Š 4  var isStudent = true;                                                   β–Ž -β–Š 5  β–Ž -β–Š 6  // Template literalsβ–Ž -β–Š 7  console.log(`Hello, ${name}! You are ${age} years old.`);               β–Ž -β–Š 8  β–Ž -β–Š 9  // Conditional statementsβ–Ž -β–Š10  if (age >= 18 && isStudent) {                                           β–Ž -β–Š11    console.log("You are an adult student.");                             β–Ž -β–Š12  elseif (age >= 18) {                                                 β–Ž -β–Š13    console.log("You are an adult.");                                     β–Ž -β–Š14  else {                                                                β–Ž -β–Š15    console.log("You are a minor.");                                      β–Ž -β–Š16  }                                                                       β–Ž -β–Š17  β–Ž -β–Š18  // Arrays and array methodsβ–Ž -β–Š19  const numbers = [12345];                                        β–Ž -β–Š20  const doubledNumbers = numbers.map((num) => num * 2);                   β–Ž -β–Š21  console.log("Doubled numbers:", doubledNumbers);                        β–Ž -β–Š22  β–Ž -β–Š23  // Objectsβ–Ž -β–Š24  const person = {                                                        β–Ž -β–Š25    firstName: "John",                                                    β–Ž -β–Š26    lastName: "Doe",                                                      β–Ž -β–Š27    getFullName() {                                                       β–Ž -β–Š28  return`${this.firstName}${this.lastName}`;                        β–Ž -β–Š29    },                                                                    β–Ž -β–Š30  };                                                                      β–Ž -β–Š31  console.log("Full name:", person.getFullName());                        β–Ž -β–Š32  β–Ž -β–Š33  // Classesβ–Ž -β–Š34  class Rectangle {                                                       β–Ž -β–Š35    constructor(width, height) {                                          β–Ž -β–Š36      this.width = width;                                                 β–Ž -β–Š37      this.height = height;                                               β–Ž -β–Š38    }                                                                     β–Ž -β–Š39  β–Ž -β–Š40    getArea() {                                                           β–Ž -β–Š41  return this.width * this.height;                                    β–Ž -β–Š42    }                                                                     β–Ž -β–Š43  }                                                                       β–Ž -β–Š44  const rectangle = new Rectangle(53);                                  β–Ž -β–Š45  console.log("Rectangle area:", rectangle.getArea());                    β–Ž -β–Š46  β–Ž -β–Š47  // Async/Await and Promisesβ–Ž -β–Š48  asyncfunctionfetchData() {                                            β–Ž -β–Š49  try {                                                                 β–Ž -β–Š50  const response = awaitfetch("https://api.example.com/data");       β–Ž -β–Š51  const data = await response.json();                                 β–Ž -β–Š52      console.log("Fetched data:", data);                                 β–Ž -β–Š53    } catch (error) {                                                     β–Ž -β–Š54      console.error("Error:", error);                                     β–Ž -β–Š55    }                                                                     β–Ž -β–Š56  }                                                                       β–Ž -β–Š57  fetchData();                                                            β–Ž -β–Š58  β–Ž -β–Š59  // Arrow functionsβ–Ž -β–Š60  constgreet = (name) => {                                               β–Ž -β–Š61    console.log(`Hello, ${name}!`);                                       β–Ž -β–Š62  };                                                                      β–Ž -β–Š63  greet("Alice");                                                         β–Ž -β–Š64  β–Ž -β–Š65  // Destructuring assignmentβ–Ž -β–Š66  const [a, b, ...rest] = [12345];                                β–Ž -β–Š67  console.log(a, b, rest);                                                β–Ž -β–Š68  β–Ž -β–Š69  // Spread operatorβ–Ž -β–Š70  const arr1 = [123];                                                 β–Ž -β–Š71  const arr2 = [456];                                                 β–Ž -β–Š72  const combinedArr = [...arr1, ...arr2];                                 β–Ž -β–Š73  console.log("Combined array:", combinedArr);                            β–Ž -β–Š74  β–Ž -β–Š75  // Ternary operatorβ–Ž -β–Š76  const message = age >= 18 ? "You are an adult." : "You are a minor.";   β–Ž -β–Š77  console.log(message);                                                   β–Ž -β–Š78  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  // Variable declarationsβ–Ž +β–Š 2  const name = "John";                                                    β–Ž +β–Š 3  let age = 30;                                                           β–Ž +β–Š 4  var isStudent = true;                                                   β–Ž +β–Š 5  β–Ž +β–Š 6  // Template literalsβ–Ž +β–Š 7  console.log(`Hello, ${name}! You are ${age} years old.`);               β–Ž +β–Š 8  β–Ž +β–Š 9  // Conditional statementsβ–Ž +β–Š10  if (age >= 18 && isStudent) {                                           β–Ž +β–Š11    console.log("You are an adult student.");                             β–Ž +β–Š12  elseif (age >= 18) {                                                 β–Ž +β–Š13    console.log("You are an adult.");                                     β–Ž +β–Š14  else {                                                                β–Ž +β–Š15    console.log("You are a minor.");                                      β–Ž +β–Š16  }                                                                       β–Ž +β–Š17  β–Ž +β–Š18  // Arrays and array methodsβ–Ž +β–Š19  const numbers = [12345];                                        β–Ž +β–Š20  const doubledNumbers = numbers.map((num) => num * 2);                   β–Ž +β–Š21  console.log("Doubled numbers:", doubledNumbers);                        β–Ž +β–Š22  β–Ž +β–Š23  // Objectsβ–Ž +β–Š24  const person = {                                                        β–Ž +β–Š25    firstName: "John",                                                    β–Ž +β–Š26    lastName: "Doe",                                                      β–Ž +β–Š27    getFullName() {                                                       β–Ž +β–Š28  return`${this.firstName}${this.lastName}`;                        β–Ž +β–Š29    },                                                                    β–Ž +β–Š30  };                                                                      β–Ž +β–Š31  console.log("Full name:", person.getFullName());                        β–Ž +β–Š32  β–Ž +β–Š33  // Classesβ–Ž +β–Š34  class Rectangle {                                                       β–Ž +β–Š35    constructor(width, height) {                                          β–Ž +β–Š36      this.width = width;                                                 β–Ž +β–Š37      this.height = height;                                               β–Ž +β–Š38    }                                                                     β–Ž +β–Š39  β–Ž +β–Š40    getArea() {                                                           β–Ž +β–Š41  return this.width * this.height;                                    β–Ž +β–Š42    }                                                                     β–Ž +β–Š43  }                                                                       β–Ž +β–Š44  const rectangle = new Rectangle(53);                                  β–Ž +β–Š45  console.log("Rectangle area:", rectangle.getArea());                    β–Ž +β–Š46  β–Ž +β–Š47  // Async/Await and Promisesβ–Ž +β–Š48  asyncfunctionfetchData() {                                            β–Ž +β–Š49  try {                                                                 β–Ž +β–Š50  const response = awaitfetch("https://api.example.com/data");       β–Ž +β–Š51  const data = await response.json();                                 β–Ž +β–Š52      console.log("Fetched data:", data);                                 β–Ž +β–Š53    } catch (error) {                                                     β–Ž +β–Š54      console.error("Error:", error);                                     β–Ž +β–Š55    }                                                                     β–Ž +β–Š56  }                                                                       β–Ž +β–Š57  fetchData();                                                            β–Ž +β–Š58  β–Ž +β–Š59  // Arrow functionsβ–Ž +β–Š60  constgreet = (name) => {                                               β–Ž +β–Š61    console.log(`Hello, ${name}!`);                                       β–Ž +β–Š62  };                                                                      β–Ž +β–Š63  greet("Alice");                                                         β–Ž +β–Š64  β–Ž +β–Š65  // Destructuring assignmentβ–Ž +β–Š66  const [a, b, ...rest] = [12345];                                β–Ž +β–Š67  console.log(a, b, rest);                                                β–Ž +β–Š68  β–Ž +β–Š69  // Spread operatorβ–Ž +β–Š70  const arr1 = [123];                                                 β–Ž +β–Š71  const arr2 = [456];                                                 β–Ž +β–Š72  const combinedArr = [...arr1, ...arr2];                                 β–Ž +β–Š73  console.log("Combined array:", combinedArr);                            β–Ž +β–Š74  β–Ž +β–Š75  // Ternary operatorβ–Ž +β–Š76  const message = age >= 18 ? "You are an adult." : "You are a minor.";   β–Ž +β–Š77  console.log(message);                                                   β–Ž +β–Š78  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg index 5cf7309fde..4106c5cc8d 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg @@ -19,329 +19,328 @@ font-weight: 700; } - .terminal-1784849415-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1784849415-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1784849415-r1 { fill: #121212 } -.terminal-1784849415-r2 { fill: #0178d4 } -.terminal-1784849415-r3 { fill: #c5c8c6 } -.terminal-1784849415-r4 { fill: #c2c2bf } -.terminal-1784849415-r5 { fill: #272822;font-weight: bold } -.terminal-1784849415-r6 { fill: #f92672;font-weight: bold } -.terminal-1784849415-r7 { fill: #f8f8f2 } -.terminal-1784849415-r8 { fill: #90908a } -.terminal-1784849415-r9 { fill: #90908a;font-weight: bold } -.terminal-1784849415-r10 { fill: #272822 } -.terminal-1784849415-r11 { fill: #003054 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822;font-weight: bold } +.terminal-r6 { fill: #f92672;font-weight: bold } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #272822 } +.terminal-r10 { fill: #003054 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  Heading β–Ž -β–Š 2  =======β–Ž -β–Š 3  β–Ž -β–Š 4  Sub-heading β–Ž -β–Š 5  -----------β–Ž -β–Š 6  β–Ž -β–Š 7  ###Headingβ–Ž -β–Š 8  β–Ž -β–Š 9  ####H4 Headingβ–Ž -β–Š10  β–Ž -β–Š11  #####H5 Headingβ–Ž -β–Š12  β–Ž -β–Š13  ######H6 Headingβ–Ž -β–Š14  β–Ž -β–Š15  β–Ž -β–Š16  Paragraphs are separated                                                β–Ž -β–Š17  by a blank line.                                                        β–Ž -β–Š18  β–Ž -β–Š19  Two spaces at the end of a line                                         β–Ž -β–Š20  produces a line break.                                                  β–Ž -β–Š21  β–Ž -β–Š22  Text attributes _italic_,                                               β–Ž -β–Š23  **bold**, `monospace`.                                                  β–Ž -β–Š24  β–Ž -β–Š25  Horizontal rule:                                                        β–Ž -β–Š26  β–Ž -β–Š27  --- β–Ž -β–Š28   β–Ž -β–Š29  Bullet list:                                                            β–Ž -β–Š30  β–Ž -β–Š31    * apples                                                              β–Ž -β–Š32  oranges                                                             β–Ž -β–Š33  pears                                                               β–Ž -β–Š34  β–Ž -β–Š35  Numbered list:                                                          β–Ž -β–Š36  β–Ž -β–Š37    1. lather                                                             β–Ž -β–Š38  2. rinse                                                              β–Ž -β–Š39  3. repeat                                                             β–Ž -β–Š40  β–Ž -β–Š41  An [example](http://example.com).                                       β–Ž -β–Š42  β–Ž -β–Š43  > Markdown uses email-style > characters for blockquoting.              β–Ž -β–Š44  >                                                                       β–Ž -β–Š45  > Lorem ipsum                                                           β–Ž -β–Š46  β–Ž -β–Š47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress.β–Ž -β–Š48  β–Ž -β–Š49  β–Ž -β–Š50  ```                                                                     β–Ž -β–Š51  a=1                                                                     β–Ž -β–Š52  ```                                                                     β–Ž -β–Š53  β–Ž -β–Š54  ```python                                                               β–Ž -β–Š55  import this                                                             β–Ž -β–Š56  ```                                                                     β–Ž -β–Š57  β–Ž -β–Š58  ```somelang                                                             β–Ž -β–Š59  foobar                                                                  β–Ž -β–Š60  ```                                                                     β–Ž -β–Š61  β–Ž -β–Š62      import this                                                         β–Ž -β–Š63  β–Ž -β–Š64  β–Ž -β–Š65  1. List item                                                            β–Ž -β–Š66  β–Ž -β–Š67         Code block                                                       β–Ž -β–Š68  β–Ž -β–Šβ–β–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  Headingβ–Ž +β–Š 2  =======β–Ž +β–Š 3  β–Ž +β–Š 4  Sub-headingβ–Ž +β–Š 5  -----------β–Ž +β–Š 6  β–Ž +β–Š 7  ###Headingβ–Ž +β–Š 8  β–Ž +β–Š 9  ####H4 Headingβ–Ž +β–Š10  β–Ž +β–Š11  #####H5 Headingβ–Ž +β–Š12  β–Ž +β–Š13  ######H6 Headingβ–Ž +β–Š14  β–Ž +β–Š15  β–Ž +β–Š16  Paragraphs are separated                                                β–Ž +β–Š17  by a blank line.                                                        β–Ž +β–Š18  β–Ž +β–Š19  Two spaces at the end of a line                                         β–Ž +β–Š20  produces a line break.                                                  β–Ž +β–Š21  β–Ž +β–Š22  Text attributes _italic_,                                               β–Ž +β–Š23  **bold**, `monospace`.                                                  β–Ž +β–Š24  β–Ž +β–Š25  Horizontal rule:                                                        β–Ž +β–Š26  β–Ž +β–Š27  ---β–Ž +β–Š28  β–Ž +β–Š29  Bullet list:                                                            β–Ž +β–Š30  β–Ž +β–Š31    * apples                                                              β–Ž +β–Š32  oranges                                                             β–Ž +β–Š33  pears                                                               β–Ž +β–Š34  β–Ž +β–Š35  Numbered list:                                                          β–Ž +β–Š36  β–Ž +β–Š37    1. lather                                                             β–Ž +β–Š38  2. rinse                                                              β–Ž +β–Š39  3. repeat                                                             β–Ž +β–Š40  β–Ž +β–Š41  An [example](http://example.com).                                       β–Ž +β–Š42  β–Ž +β–Š43  > Markdown uses email-style > characters for blockquoting.              β–Ž +β–Š44  >                                                                       β–Ž +β–Š45  > Lorem ipsum                                                           β–Ž +β–Š46  β–Ž +β–Š47  ![progress](https://github.com/textualize/rich/raw/master/imgs/progress.β–Ž +β–Š48  β–Ž +β–Š49  β–Ž +β–Š50  ```                                                                     β–Ž +β–Š51  a=1                                                                     β–Ž +β–Š52  ```                                                                     β–Ž +β–Š53  β–Ž +β–Š54  ```python                                                               β–Ž +β–Š55  import this                                                             β–Ž +β–Š56  ```                                                                     β–Ž +β–Š57  β–Ž +β–Š58  ```somelang                                                             β–Ž +β–Š59  foobar                                                                  β–Ž +β–Š60  ```                                                                     β–Ž +β–Š61  β–Ž +β–Š62      import this                                                         β–Ž +β–Š63  β–Ž +β–Š64  β–Ž +β–Š65  1. List item                                                            β–Ž +β–Š66  β–Ž +β–Š67         Code block                                                       β–Ž +β–Š68  β–Ž +β–Šβ–β–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[python].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[python].svg index 92ffdc54f9..7fdee992fd 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[python].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[python].svg @@ -19,375 +19,375 @@ font-weight: 700; } - .terminal-202856356-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-202856356-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-202856356-r1 { fill: #121212 } -.terminal-202856356-r2 { fill: #0178d4 } -.terminal-202856356-r3 { fill: #c5c8c6 } -.terminal-202856356-r4 { fill: #c2c2bf } -.terminal-202856356-r5 { fill: #272822 } -.terminal-202856356-r6 { fill: #f92672 } -.terminal-202856356-r7 { fill: #f8f8f2 } -.terminal-202856356-r8 { fill: #90908a } -.terminal-202856356-r9 { fill: #75715e } -.terminal-202856356-r10 { fill: #e6db74 } -.terminal-202856356-r11 { fill: #ae81ff } -.terminal-202856356-r12 { fill: #a6e22e } -.terminal-202856356-r13 { fill: #003054 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #f92672 } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #75715e } +.terminal-r10 { fill: #e6db74 } +.terminal-r11 { fill: #ae81ff } +.terminal-r12 { fill: #a6e22e } +.terminal-r13 { fill: #003054 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  import math                                                             β–Ž -β–Š 2  from os import path                                                     β–Ž -β–Š 3  β–Ž -β–Š 4  # I'm a comment :)β–Ž -β–Š 5  β–Ž -β–Š 6  string_var = "Hello, world!"β–Ž -β–Š 7  int_var = 42β–Ž -β–Š 8  float_var = 3.14β–Ž -β–Š 9  complex_var = 1 + 2jβ–Ž -β–Š10  β–Ž -β–Š11  list_var = [12345]                                              β–Ž -β–Š12  tuple_var = (12345)                                             β–Ž -β–Š13  set_var = {12345}                                               β–Ž -β–Š14  dict_var = {"a"1"b"2"c"3}                                     β–Ž -β–Š15  β–Ž -β–Š16  deffunction_no_args():                                                 β–Ž -β–Š17  return"No arguments"β–Ž -β–Š18  β–Ž -β–Š19  deffunction_with_args(a, b):                                           β–Ž -β–Š20  return a + b                                                        β–Ž -β–Š21  β–Ž -β–Š22  deffunction_with_default_args(a=0, b=0):                               β–Ž -β–Š23  return a * b                                                        β–Ž -β–Š24  β–Ž -β–Š25  lambda_func = lambda x: x**2β–Ž -β–Š26  β–Ž -β–Š27  if int_var == 42:                                                       β–Ž -β–Š28  print("It's the answer!")                                           β–Ž -β–Š29  elif int_var < 42:                                                      β–Ž -β–Š30  print("Less than the answer.")                                      β–Ž -β–Š31  else:                                                                   β–Ž -β–Š32  print("Greater than the answer.")                                   β–Ž -β–Š33  β–Ž -β–Š34  for index, value inenumerate(list_var):                                β–Ž -β–Š35  print(f"Index: {index}, Value: {value}")                            β–Ž -β–Š36  β–Ž -β–Š37  counter = 0β–Ž -β–Š38  while counter < 5:                                                      β–Ž -β–Š39  print(f"Counter value: {counter}")                                  β–Ž -β–Š40      counter += 1β–Ž -β–Š41  β–Ž -β–Š42  squared_numbers = [x**2for x inrange(10if x % 2 == 0]               β–Ž -β–Š43  β–Ž -β–Š44  try:                                                                    β–Ž -β–Š45      result = 10 / 0β–Ž -β–Š46  except ZeroDivisionError:                                               β–Ž -β–Š47  print("Cannot divide by zero!")                                     β–Ž -β–Š48  finally:                                                                β–Ž -β–Š49  print("End of try-except block.")                                   β–Ž -β–Š50  β–Ž -β–Š51  classAnimal:                                                           β–Ž -β–Š52  def__init__(self, name):                                           β–Ž -β–Š53          self.name = name                                                β–Ž -β–Š54  β–Ž -β–Š55  defspeak(self):                                                    β–Ž -β–Š56  raiseNotImplementedError("Subclasses must implement this methodβ–Ž -β–Š57  β–Ž -β–Š58  classDog(Animal):                                                      β–Ž -β–Š59  defspeak(self):                                                    β–Ž -β–Š60  returnf"{self.name} says Woof!"β–Ž -β–Š61  β–Ž -β–Š62  deffibonacci(n):                                                       β–Ž -β–Š63      a, b = 01β–Ž -β–Š64  for _ inrange(n):                                                  β–Ž -β–Š65  yield a                                                         β–Ž -β–Š66          a, b = b, a + b                                                 β–Ž -β–Š67  β–Ž -β–Š68  for num infibonacci(5):                                                β–Ž -β–Š69  print(num)                                                          β–Ž -β–Š70  β–Ž -β–Š71  withopen('test.txt''w'as f:                                        β–Ž -β–Š72      f.write("Testing with statement.")                                  β–Ž -β–Š73  β–Ž -β–Š74  @my_decorator                                                           β–Ž -β–Š75  defsay_hello():                                                        β–Ž -β–Š76  print("Hello!")                                                     β–Ž -β–Š77  β–Ž -β–Š78  say_hello()                                                             β–Ž -β–Š79  β–Ž -β–Šβ–Žβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  import math                                                             β–Ž +β–Š 2  from os import path                                                     β–Ž +β–Š 3  β–Ž +β–Š 4  # I'm a comment :)β–Ž +β–Š 5  β–Ž +β–Š 6  string_var = "Hello, world!"β–Ž +β–Š 7  int_var = 42β–Ž +β–Š 8  float_var = 3.14β–Ž +β–Š 9  complex_var = 1 + 2jβ–Ž +β–Š10  β–Ž +β–Š11  list_var = [12345]                                              β–Ž +β–Š12  tuple_var = (12345)                                             β–Ž +β–Š13  set_var = {12345}                                               β–Ž +β–Š14  dict_var = {"a"1"b"2"c"3}                                     β–Ž +β–Š15  β–Ž +β–Š16  deffunction_no_args():                                                 β–Ž +β–Š17  return"No arguments"β–Ž +β–Š18  β–Ž +β–Š19  deffunction_with_args(a, b):                                           β–Ž +β–Š20  return a + b                                                        β–Ž +β–Š21  β–Ž +β–Š22  deffunction_with_default_args(a=0, b=0):                               β–Ž +β–Š23  return a * b                                                        β–Ž +β–Š24  β–Ž +β–Š25  lambda_func = lambda x: x**2β–Ž +β–Š26  β–Ž +β–Š27  if int_var == 42:                                                       β–Ž +β–Š28  print("It's the answer!")                                           β–Ž +β–Š29  elif int_var < 42:                                                      β–Ž +β–Š30  print("Less than the answer.")                                      β–Ž +β–Š31  else:                                                                   β–Ž +β–Š32  print("Greater than the answer.")                                   β–Ž +β–Š33  β–Ž +β–Š34  for index, value inenumerate(list_var):                                β–Ž +β–Š35  print(f"Index: {index}, Value: {value}")                            β–Ž +β–Š36  β–Ž +β–Š37  counter = 0β–Ž +β–Š38  while counter < 5:                                                      β–Ž +β–Š39  print(f"Counter value: {counter}")                                  β–Ž +β–Š40      counter += 1β–Ž +β–Š41  β–Ž +β–Š42  squared_numbers = [x**2for x inrange(10if x % 2 == 0]               β–Ž +β–Š43  β–Ž +β–Š44  try:                                                                    β–Ž +β–Š45      result = 10 / 0β–Ž +β–Š46  exceptZeroDivisionError:                                               β–Ž +β–Š47  print("Cannot divide by zero!")                                     β–Ž +β–Š48  finally:                                                                β–Ž +β–Š49  print("End of try-except block.")                                   β–Ž +β–Š50  β–Ž +β–Š51  classAnimal:                                                           β–Ž +β–Š52  def__init__(self, name):                                           β–Ž +β–Š53          self.name = name                                                β–Ž +β–Š54  β–Ž +β–Š55  defspeak(self):                                                    β–Ž +β–Š56  raiseNotImplementedError("Subclasses must implement this methodβ–Ž +β–Š57  β–Ž +β–Š58  classDog(Animal):                                                      β–Ž +β–Š59  defspeak(self):                                                    β–Ž +β–Š60  returnf"{self.name} says Woof!"β–Ž +β–Š61  β–Ž +β–Š62  deffibonacci(n):                                                       β–Ž +β–Š63      a, b = 01β–Ž +β–Š64  for _ inrange(n):                                                  β–Ž +β–Š65  yield a                                                         β–Ž +β–Š66          a, b = b, a + b                                                 β–Ž +β–Š67  β–Ž +β–Š68  for num infibonacci(5):                                                β–Ž +β–Š69  print(num)                                                          β–Ž +β–Š70  β–Ž +β–Š71  withopen('test.txt''w'as f:                                        β–Ž +β–Š72      f.write("Testing with statement.")                                  β–Ž +β–Š73  β–Ž +β–Š74  @my_decorator                                                           β–Ž +β–Š75  defsay_hello():                                                        β–Ž +β–Š76  print("Hello!")                                                     β–Ž +β–Š77  β–Ž +β–Š78  say_hello()                                                             β–Ž +β–Š79  β–Ž +β–Šβ–Žβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[rust].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[rust].svg index 5552dc5849..da8e8479f7 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[rust].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[rust].svg @@ -19,479 +19,479 @@ font-weight: 700; } - .terminal-1133178079-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1133178079-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1133178079-r1 { fill: #121212 } -.terminal-1133178079-r2 { fill: #0178d4 } -.terminal-1133178079-r3 { fill: #c5c8c6 } -.terminal-1133178079-r4 { fill: #c2c2bf } -.terminal-1133178079-r5 { fill: #272822 } -.terminal-1133178079-r6 { fill: #f92672 } -.terminal-1133178079-r7 { fill: #f8f8f2 } -.terminal-1133178079-r8 { fill: #a6e22e } -.terminal-1133178079-r9 { fill: #90908a } -.terminal-1133178079-r10 { fill: #75715e } -.terminal-1133178079-r11 { fill: #66d9ef;font-style: italic; } -.terminal-1133178079-r12 { fill: #e6db74 } -.terminal-1133178079-r13 { fill: #003054 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #f92672 } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #75715e } +.terminal-r10 { fill: #66d9ef;font-style: italic; } +.terminal-r11 { fill: #a6e22e } +.terminal-r12 { fill: #e6db74 } +.terminal-r13 { fill: #003054 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š  1  usestd::collections::HashMap;                                         β–Ž -β–Š  2  β–Ž -β–Š  3  // Constantsβ–Ž -β–Š  4  const PI: f64 = 3.14159;                                               β–Ž -β–Š  5  β–Ž -β–Š  6  // Structsβ–Ž -β–Š  7  structRectangle {                                                     β–Ž -β–Š  8      width: u32,                                                        β–Ž -β–Š  9      height: u32,                                                       β–Ž -β–Š 10  }                                                                      β–Ž -β–Š 11  β–Ž -β–Š 12  implRectangle {                                                       β–Ž -β–Š 13  fnarea(&self) -> u32 {                                            β–Ž -β–Š 14          self.width * self.height                                       β–Ž -β–Š 15      }                                                                  β–Ž -β–Š 16  }                                                                      β–Ž -β–Š 17  β–Ž -β–Š 18  // Enumsβ–Ž -β–Š 19  enumResult<TE> {                                                    β–Ž -β–Š 20      Ok(T),                                                             β–Ž -β–Š 21      Err(E),                                                            β–Ž -β–Š 22  }                                                                      β–Ž -β–Š 23  β–Ž -β–Š 24  // Functionsβ–Ž -β–Š 25  fngreet(name: &str) {                                                 β–Ž -β–Š 26      println!("Hello, {}!", name);                                      β–Ž -β–Š 27  }                                                                      β–Ž -β–Š 28  β–Ž -β–Š 29  fnmain() {                                                            β–Ž -β–Š 30  // Variablesβ–Ž -β–Š 31  let name = "John";                                                 β–Ž -β–Š 32  letmut age = 30;                                                  β–Ž -β–Š 33  let is_student = true;                                             β–Ž -β–Š 34  β–Ž -β–Š 35  // Printing variablesβ–Ž -β–Š 36      println!("Hello, {}! You are {} years old.", name, age);           β–Ž -β–Š 37  β–Ž -β–Š 38  // Conditional statementsβ–Ž -β–Š 39  if age >= 18 && is_student {                                       β–Ž -β–Š 40          println!("You are an adult student.");                         β–Ž -β–Š 41      } elseif age >= 18 {                                              β–Ž -β–Š 42          println!("You are an adult.");                                 β–Ž -β–Š 43      } else {                                                           β–Ž -β–Š 44          println!("You are a minor.");                                  β–Ž -β–Š 45      }                                                                  β–Ž -β–Š 46  β–Ž -β–Š 47  // Arraysβ–Ž -β–Š 48  let numbers = [12345];                                     β–Ž -β–Š 49      println!("Numbers: {:?}", numbers);                                β–Ž -β–Š 50  β–Ž -β–Š 51  // Vectorsβ–Ž -β–Š 52  letmut fruits = vec!["apple""banana""orange"];                β–Ž -β–Š 53      fruits.push("grape");                                              β–Ž -β–Š 54      println!("Fruits: {:?}", fruits);                                  β–Ž -β–Š 55  β–Ž -β–Š 56  // Loopsβ–Ž -β–Š 57  for num in &numbers {                                              β–Ž -β–Š 58          println!("Number: {}", num);                                   β–Ž -β–Š 59      }                                                                  β–Ž -β–Š 60  β–Ž -β–Š 61  // Pattern matchingβ–Ž -β–Š 62  let result = Result::Ok(42);                                       β–Ž -β–Š 63  match result {                                                     β–Ž -β–Š 64  Result::Ok(value) => println!("Value: {}", value),             β–Ž -β–Š 65  Result::Err(error) => println!("Error: {:?}", error),          β–Ž -β–Š 66      }                                                                  β–Ž -β–Š 67  β–Ž -β–Š 68  // Ownership and borrowingβ–Ž -β–Š 69  let s1 = String::from("hello");                                    β–Ž -β–Š 70  let s2 = s1.clone();                                               β–Ž -β–Š 71      println!("s1: {}, s2: {}", s1, s2);                                β–Ž -β–Š 72  β–Ž -β–Š 73  // Referencesβ–Ž -β–Š 74  let rect = Rectangle {                                             β–Ž -β–Š 75          width: 10,                                                     β–Ž -β–Š 76          height: 20,                                                    β–Ž -β–Š 77      };                                                                 β–Ž -β–Š 78      println!("Rectangle area: {}", rect.area());                       β–Ž -β–Š 79  β–Ž -β–Š 80  // Hash mapsβ–Ž -β–Š 81  letmut scores = HashMap::new();                                   β–Ž -β–Š 82      scores.insert("Alice"100);                                       β–Ž -β–Š 83      scores.insert("Bob"80);                                          β–Ž -β–Š 84      println!("Alice's score: {}", scores["Alice"]);                    β–Ž -β–Š 85  β–Ž -β–Š 86  // Closuresβ–Ž -β–Š 87  let square = |num: i32| num * num;                                 β–Ž -β–Š 88      println!("Square of 5: {}", square(5));                            β–Ž -β–Š 89  β–Ž -β–Š 90  // Traitsβ–Ž -β–Š 91  traitPrintable {                                                  β–Ž -β–Š 92  fnprint(&self);                                               β–Ž -β–Š 93      }                                                                  β–Ž -β–Š 94  β–Ž -β–Š 95  implPrintableforRectangle {                                     β–Ž -β–Š 96  fnprint(&self) {                                              β–Ž -β–Š 97              println!("Rectangle: width={}, height={}", self.width, selfβ–Ž -β–Š 98          }                                                              β–Ž -β–Š 99      }                                                                  β–Ž -β–Š100      rect.print();                                                      β–Ž -β–Š101  β–Ž -β–Š102  // Modulesβ–Ž -β–Š103  greet("Alice");                                                    β–Ž -β–Š104  }                                                                      β–Ž -β–Š105  β–Ž -β–Šβ–Žβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š  1  use std::collections::HashMap;                                         β–Ž +β–Š  2  β–Ž +β–Š  3  // Constantsβ–Ž +β–Š  4  const PI: f64 = 3.14159;                                               β–Ž +β–Š  5  β–Ž +β–Š  6  // Structsβ–Ž +β–Š  7  structRectangle {                                                     β–Ž +β–Š  8      width: u32,                                                        β–Ž +β–Š  9      height: u32,                                                       β–Ž +β–Š 10  }                                                                      β–Ž +β–Š 11  β–Ž +β–Š 12  implRectangle {                                                       β–Ž +β–Š 13  fnarea(&self) -> u32 {                                            β–Ž +β–Š 14          self.width * self.height                                       β–Ž +β–Š 15      }                                                                  β–Ž +β–Š 16  }                                                                      β–Ž +β–Š 17  β–Ž +β–Š 18  // Enumsβ–Ž +β–Š 19  enumResult<TE> {                                                    β–Ž +β–Š 20      Ok(T),                                                             β–Ž +β–Š 21      Err(E),                                                            β–Ž +β–Š 22  }                                                                      β–Ž +β–Š 23  β–Ž +β–Š 24  // Functionsβ–Ž +β–Š 25  fngreet(name: &str) {                                                 β–Ž +β–Š 26      println!("Hello, {}!", name);                                      β–Ž +β–Š 27  }                                                                      β–Ž +β–Š 28  β–Ž +β–Š 29  fnmain() {                                                            β–Ž +β–Š 30  // Variablesβ–Ž +β–Š 31  let name = "John";                                                 β–Ž +β–Š 32  letmut age = 30;                                                  β–Ž +β–Š 33  let is_student = true;                                             β–Ž +β–Š 34  β–Ž +β–Š 35  // Printing variablesβ–Ž +β–Š 36      println!("Hello, {}! You are {} years old.", name, age);           β–Ž +β–Š 37  β–Ž +β–Š 38  // Conditional statementsβ–Ž +β–Š 39  if age >= 18 && is_student {                                       β–Ž +β–Š 40          println!("You are an adult student.");                         β–Ž +β–Š 41      } elseif age >= 18 {                                              β–Ž +β–Š 42          println!("You are an adult.");                                 β–Ž +β–Š 43      } else {                                                           β–Ž +β–Š 44          println!("You are a minor.");                                  β–Ž +β–Š 45      }                                                                  β–Ž +β–Š 46  β–Ž +β–Š 47  // Arraysβ–Ž +β–Š 48  let numbers = [12345];                                     β–Ž +β–Š 49      println!("Numbers: {:?}", numbers);                                β–Ž +β–Š 50  β–Ž +β–Š 51  // Vectorsβ–Ž +β–Š 52  letmut fruits = vec!["apple""banana""orange"];                β–Ž +β–Š 53      fruits.push("grape");                                              β–Ž +β–Š 54      println!("Fruits: {:?}", fruits);                                  β–Ž +β–Š 55  β–Ž +β–Š 56  // Loopsβ–Ž +β–Š 57  for num in &numbers {                                              β–Ž +β–Š 58          println!("Number: {}", num);                                   β–Ž +β–Š 59      }                                                                  β–Ž +β–Š 60  β–Ž +β–Š 61  // Pattern matchingβ–Ž +β–Š 62  let result = Result::Ok(42);                                       β–Ž +β–Š 63  match result {                                                     β–Ž +β–Š 64  Result::Ok(value) => println!("Value: {}", value),             β–Ž +β–Š 65  Result::Err(error) => println!("Error: {:?}", error),          β–Ž +β–Š 66      }                                                                  β–Ž +β–Š 67  β–Ž +β–Š 68  // Ownership and borrowingβ–Ž +β–Š 69  let s1 = String::from("hello");                                    β–Ž +β–Š 70  let s2 = s1.clone();                                               β–Ž +β–Š 71      println!("s1: {}, s2: {}", s1, s2);                                β–Ž +β–Š 72  β–Ž +β–Š 73  // Referencesβ–Ž +β–Š 74  let rect = Rectangle {                                             β–Ž +β–Š 75          width: 10,                                                     β–Ž +β–Š 76          height: 20,                                                    β–Ž +β–Š 77      };                                                                 β–Ž +β–Š 78      println!("Rectangle area: {}", rect.area());                       β–Ž +β–Š 79  β–Ž +β–Š 80  // Hash mapsβ–Ž +β–Š 81  letmut scores = HashMap::new();                                   β–Ž +β–Š 82      scores.insert("Alice"100);                                       β–Ž +β–Š 83      scores.insert("Bob"80);                                          β–Ž +β–Š 84      println!("Alice's score: {}", scores["Alice"]);                    β–Ž +β–Š 85  β–Ž +β–Š 86  // Closuresβ–Ž +β–Š 87  let square = |num: i32| num * num;                                 β–Ž +β–Š 88      println!("Square of 5: {}", square(5));                            β–Ž +β–Š 89  β–Ž +β–Š 90  // Traitsβ–Ž +β–Š 91  traitPrintable {                                                  β–Ž +β–Š 92  fnprint(&self);                                               β–Ž +β–Š 93      }                                                                  β–Ž +β–Š 94  β–Ž +β–Š 95  implPrintableforRectangle {                                     β–Ž +β–Š 96  fnprint(&self) {                                              β–Ž +β–Š 97              println!("Rectangle: width={}, height={}", self.width, selfβ–Ž +β–Š 98          }                                                              β–Ž +β–Š 99      }                                                                  β–Ž +β–Š100      rect.print();                                                      β–Ž +β–Š101  β–Ž +β–Š102  // Modulesβ–Ž +β–Š103  greet("Alice");                                                    β–Ž +β–Š104  }                                                                      β–Ž +β–Š105  β–Ž +β–Šβ–Žβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg index 7d9ce1aeb3..31f74b433e 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg @@ -19,130 +19,130 @@ font-weight: 700; } - .terminal-1843935949-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1843935949-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1843935949-r1 { fill: #121212 } -.terminal-1843935949-r2 { fill: #0178d4 } -.terminal-1843935949-r3 { fill: #c5c8c6 } -.terminal-1843935949-r4 { fill: #c2c2bf } -.terminal-1843935949-r5 { fill: #272822 } -.terminal-1843935949-r6 { fill: #f8f8f2 } -.terminal-1843935949-r7 { fill: #f92672 } -.terminal-1843935949-r8 { fill: #ae81ff } -.terminal-1843935949-r9 { fill: #90908a } -.terminal-1843935949-r10 { fill: #75715e } -.terminal-1843935949-r11 { fill: #e6db74 } -.terminal-1843935949-r12 { fill: #003054 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #f8f8f2 } +.terminal-r7 { fill: #f92672 } +.terminal-r8 { fill: #ae81ff } +.terminal-r9 { fill: #90908a } +.terminal-r10 { fill: #75715e } +.terminal-r11 { fill: #e6db74 } +.terminal-r12 { fill: #003054 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  <?xml version="1.0" encoding="UTF-8"?>                                  β–Ž -β–Š 2  <!-- This is an example XML document -->β–Ž -β–Š 3  <library>                                                               β–Ž -β–Š 4      <book id="1" genre="fiction">                                       β–Ž -β–Š 5          <title>The Great Gatsby</title>                                 β–Ž -β–Š 6          <author>F. Scott Fitzgerald</author>                            β–Ž -β–Š 7          <published>1925</published>                                     β–Ž -β–Š 8          <description><![CDATA[This classic novel explores themes of wealβ–Ž -β–Š 9      </book>                                                             β–Ž -β–Š10      <book id="2" genre="non-fiction">                                   β–Ž -β–Š11          <title>Sapiens: A Brief History of Humankind</title>            β–Ž -β–Š12          <author>Yuval Noah Harari</author>                              β–Ž -β–Š13          <published>2011</published>                                     β–Ž -β–Š14          <description><![CDATA[Explores the history and impact of Homo saβ–Ž -β–Š15      </book>                                                             β–Ž -β–Š16  <!-- Another book can be added here -->β–Ž -β–Š17  </library>                                                              β–Ž -β–Š18  β–Ž -β–Šβ–Œβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  <?xml version="1.0" encoding="UTF-8"?>                                  β–Ž +β–Š 2  <!-- This is an example XML document -->β–Ž +β–Š 3  <library>                                                               β–Ž +β–Š 4      <book id="1" genre="fiction">                                       β–Ž +β–Š 5          <title>The Great Gatsby</title>                                 β–Ž +β–Š 6          <author>F. Scott Fitzgerald</author>                            β–Ž +β–Š 7          <published>1925</published>                                     β–Ž +β–Š 8          <description><![CDATA[This classic novel explores themes of wealβ–Ž +β–Š 9      </book>                                                             β–Ž +β–Š10      <book id="2" genre="non-fiction">                                   β–Ž +β–Š11          <title>Sapiens: A Brief History of Humankind</title>            β–Ž +β–Š12          <author>Yuval Noah Harari</author>                              β–Ž +β–Š13          <published>2011</published>                                     β–Ž +β–Š14          <description><![CDATA[Explores the history and impact of Homo saβ–Ž +β–Š15      </book>                                                             β–Ž +β–Š16  <!-- Another book can be added here -->β–Ž +β–Š17  </library>                                                              β–Ž +β–Š18  β–Ž +β–Šβ–Œβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[yaml].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[yaml].svg index f12b6d629f..e4de99559e 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[yaml].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[yaml].svg @@ -19,210 +19,210 @@ font-weight: 700; } - .terminal-2714046411-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2714046411-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2714046411-r1 { fill: #121212 } -.terminal-2714046411-r2 { fill: #0178d4 } -.terminal-2714046411-r3 { fill: #c5c8c6 } -.terminal-2714046411-r4 { fill: #c2c2bf } -.terminal-2714046411-r5 { fill: #272822 } -.terminal-2714046411-r6 { fill: #75715e } -.terminal-2714046411-r7 { fill: #f8f8f2 } -.terminal-2714046411-r8 { fill: #90908a } -.terminal-2714046411-r9 { fill: #f92672;font-weight: bold } -.terminal-2714046411-r10 { fill: #e6db74 } -.terminal-2714046411-r11 { fill: #ae81ff } -.terminal-2714046411-r12 { fill: #66d9ef;font-style: italic; } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #c2c2bf } +.terminal-r5 { fill: #272822 } +.terminal-r6 { fill: #75715e } +.terminal-r7 { fill: #f8f8f2 } +.terminal-r8 { fill: #90908a } +.terminal-r9 { fill: #f92672;font-weight: bold } +.terminal-r10 { fill: #e6db74 } +.terminal-r11 { fill: #ae81ff } +.terminal-r12 { fill: #66d9ef;font-style: italic; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š 1  # This is a comment in YAMLβ–Ž -β–Š 2  β–Ž -β–Š 3  # Scalarsβ–Ž -β–Š 4  string"Hello, world!"β–Ž -β–Š 5  integer42β–Ž -β–Š 6  float3.14β–Ž -β–Š 7  booleantrueβ–Ž -β–Š 8  β–Ž -β–Š 9  # Sequences (Arrays)β–Ž -β–Š10  fruits:                                                                 β–Ž -β–Š11    - Appleβ–Ž -β–Š12    - Bananaβ–Ž -β–Š13    - Cherryβ–Ž -β–Š14  β–Ž -β–Š15  # Nested sequencesβ–Ž -β–Š16  persons:                                                                β–Ž -β–Š17    - nameJohnβ–Ž -β–Š18  age28β–Ž -β–Š19  is_studentfalseβ–Ž -β–Š20    - nameJaneβ–Ž -β–Š21  age22β–Ž -β–Š22  is_studenttrueβ–Ž -β–Š23  β–Ž -β–Š24  # Mappings (Dictionaries)β–Ž -β–Š25  address:                                                                β–Ž -β–Š26  street123 Main Stβ–Ž -β–Š27  cityAnytownβ–Ž -β–Š28  stateCAβ–Ž -β–Š29  zip'12345'β–Ž -β–Š30  β–Ž -β–Š31  # Multiline stringβ–Ž -β–Š32  description: |β–Ž -β–Š33    This is a multiline β–Ž -β–Š34    string in YAML.β–Ž -β–Š35  β–Ž -β–Š36  # Inline and nested collectionsβ–Ž -β–Š37  colors: { redFF0000green00FF00blue0000FF }                    β–Ž -β–Š38  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š 1  # This is a comment in YAMLβ–Ž +β–Š 2  β–Ž +β–Š 3  # Scalarsβ–Ž +β–Š 4  string"Hello, world!"β–Ž +β–Š 5  integer42β–Ž +β–Š 6  float3.14β–Ž +β–Š 7  booleantrueβ–Ž +β–Š 8  β–Ž +β–Š 9  # Sequences (Arrays)β–Ž +β–Š10  fruits:                                                                 β–Ž +β–Š11    - Appleβ–Ž +β–Š12    - Bananaβ–Ž +β–Š13    - Cherryβ–Ž +β–Š14  β–Ž +β–Š15  # Nested sequencesβ–Ž +β–Š16  persons:                                                                β–Ž +β–Š17    - nameJohnβ–Ž +β–Š18  age28β–Ž +β–Š19  is_studentfalseβ–Ž +β–Š20    - nameJaneβ–Ž +β–Š21  age22β–Ž +β–Š22  is_studenttrueβ–Ž +β–Š23  β–Ž +β–Š24  # Mappings (Dictionaries)β–Ž +β–Š25  address:                                                                β–Ž +β–Š26  street123 Main Stβ–Ž +β–Š27  cityAnytownβ–Ž +β–Š28  stateCAβ–Ž +β–Š29  zip'12345'β–Ž +β–Š30  β–Ž +β–Š31  # Multiline stringβ–Ž +β–Š32  description: |                                                          β–Ž +β–Š33    This is a multilineβ–Ž +β–Š34    string in YAML.β–Ž +β–Š35  β–Ž +β–Š36  # Inline and nested collectionsβ–Ž +β–Š37  colors: { redFF0000green00FF00blue0000FF }                    β–Ž +β–Š38  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[css].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[css].svg index 0d6b98a198..490993e813 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[css].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[css].svg @@ -19,84 +19,82 @@ font-weight: 700; } - .terminal-1378439235-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1378439235-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1378439235-r1 { fill: #121212 } -.terminal-1378439235-r2 { fill: #0178d4 } -.terminal-1378439235-r3 { fill: #c5c8c6 } -.terminal-1378439235-r4 { fill: #71716e } -.terminal-1378439235-r5 { fill: #569cd6 } -.terminal-1378439235-r6 { fill: #f8f8f2 } -.terminal-1378439235-r7 { fill: #dcdcaa } -.terminal-1378439235-r8 { fill: #cccccc } -.terminal-1378439235-r9 { fill: #9cdcfe } -.terminal-1378439235-r10 { fill: #999997;font-weight: bold } -.terminal-1378439235-r11 { fill: #b5cea8 } -.terminal-1378439235-r12 { fill: #7daf9c } -.terminal-1378439235-r13 { fill: #4ec9b0 } -.terminal-1378439235-r14 { fill: #ce9178 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #71716e } +.terminal-r5 { fill: #569cd6 } +.terminal-r6 { fill: #f8f8f2 } +.terminal-r7 { fill: #dcdcaa } +.terminal-r8 { fill: #cccccc } +.terminal-r9 { fill: #999997;font-weight: bold } +.terminal-r10 { fill: #b5cea8 } +.terminal-r11 { fill: #7daf9c } +.terminal-r12 { fill: #ce9178 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š1  defhello(name):β–Ž -β–Š2  x=123β–Ž -β–Š3  whilenotFalse:β–Ž -β–Š4  print("hello "+name)β–Ž -β–Š5  continueβ–Ž -β–Š6  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defhello(name):β–Ž +β–Š2      x =123β–Ž +β–Š3  whilenotFalse:β–Ž +β–Š4  print("hello "+ name)β–Ž +β–Š5  continueβ–Ž +β–Š6  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[dracula].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[dracula].svg index 2ec01a5ea3..c46632b650 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[dracula].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[dracula].svg @@ -19,81 +19,81 @@ font-weight: 700; } - .terminal-789685810-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-789685810-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-789685810-r1 { fill: #121212 } -.terminal-789685810-r2 { fill: #0178d4 } -.terminal-789685810-r3 { fill: #c5c8c6 } -.terminal-789685810-r4 { fill: #6272a4 } -.terminal-789685810-r5 { fill: #ff79c6 } -.terminal-789685810-r6 { fill: #f8f8f2 } -.terminal-789685810-r7 { fill: #c2c2bf;font-weight: bold } -.terminal-789685810-r8 { fill: #bd93f9 } -.terminal-789685810-r9 { fill: #282a36 } -.terminal-789685810-r10 { fill: #50fa7b } -.terminal-789685810-r11 { fill: #f1fa8c } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #6272a4 } +.terminal-r5 { fill: #ff79c6 } +.terminal-r6 { fill: #f8f8f2 } +.terminal-r7 { fill: #50fa7b } +.terminal-r8 { fill: #c2c2bf;font-weight: bold } +.terminal-r9 { fill: #bd93f9 } +.terminal-r10 { fill: #282a36 } +.terminal-r11 { fill: #f1fa8c } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š1  def hello(name):β–Ž -β–Š2      x = 123β–Ž -β–Š3  whilenotFalse:                     β–Ž -β–Š4  print("hello " + name)           β–Ž -β–Š5  continueβ–Ž -β–Š6  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defhello(name):β–Ž +β–Š2      x = 123β–Ž +β–Š3  whilenotFalse:                     β–Ž +β–Š4  print("hello " + name)           β–Ž +β–Š5  continueβ–Ž +β–Š6  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[github_light].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[github_light].svg index 8def597c67..0b33d27723 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[github_light].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[github_light].svg @@ -19,83 +19,84 @@ font-weight: 700; } - .terminal-3341902079-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3341902079-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3341902079-r1 { fill: #121212 } -.terminal-3341902079-r2 { fill: #0178d4 } -.terminal-3341902079-r3 { fill: #c5c8c6 } -.terminal-3341902079-r4 { fill: #bbbbbb } -.terminal-3341902079-r5 { fill: #cf222e } -.terminal-3341902079-r6 { fill: #24292e } -.terminal-3341902079-r7 { fill: #6639bb } -.terminal-3341902079-r8 { fill: #a4a4a4 } -.terminal-3341902079-r9 { fill: #7daf9c } -.terminal-3341902079-r10 { fill: #0450ae } -.terminal-3341902079-r11 { fill: #d73a49 } -.terminal-3341902079-r12 { fill: #fafbfc } -.terminal-3341902079-r13 { fill: #093069 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #bbbbbb } +.terminal-r5 { fill: #cf222e } +.terminal-r6 { fill: #24292e } +.terminal-r7 { fill: #6639bb } +.terminal-r8 { fill: #a4a4a4 } +.terminal-r9 { fill: #e36209 } +.terminal-r10 { fill: #0450ae } +.terminal-r11 { fill: #d73a49 } +.terminal-r12 { fill: #fafbfc } +.terminal-r13 { fill: #7daf9c } +.terminal-r14 { fill: #093069 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - + - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š1  defhello(name):β–Ž -β–Š2  x=123β–Ž -β–Š3  whilenotFalse:                     β–Ž -β–Š4  print("hello "+name)           β–Ž -β–Š5  continueβ–Ž -β–Š6  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defhello(name):β–Ž +β–Š2  x=123β–Ž +β–Š3  whilenotFalse:                     β–Ž +β–Š4  print("hello "+name)           β–Ž +β–Š5  continueβ–Ž +β–Š6  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[monokai].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[monokai].svg index f69e242c44..3e94f22d02 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[monokai].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[monokai].svg @@ -19,82 +19,82 @@ font-weight: 700; } - .terminal-2677119763-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2677119763-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2677119763-r1 { fill: #121212 } -.terminal-2677119763-r2 { fill: #0178d4 } -.terminal-2677119763-r3 { fill: #c5c8c6 } -.terminal-2677119763-r4 { fill: #90908a } -.terminal-2677119763-r5 { fill: #f92672 } -.terminal-2677119763-r6 { fill: #f8f8f2 } -.terminal-2677119763-r7 { fill: #c2c2bf } -.terminal-2677119763-r8 { fill: #ae81ff } -.terminal-2677119763-r9 { fill: #272822 } -.terminal-2677119763-r10 { fill: #66d9ef;font-style: italic; } -.terminal-2677119763-r11 { fill: #a6e22e } -.terminal-2677119763-r12 { fill: #e6db74 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #90908a } +.terminal-r5 { fill: #f92672 } +.terminal-r6 { fill: #f8f8f2 } +.terminal-r7 { fill: #a6e22e } +.terminal-r8 { fill: #c2c2bf } +.terminal-r9 { fill: #ae81ff } +.terminal-r10 { fill: #272822 } +.terminal-r11 { fill: #66d9ef;font-style: italic; } +.terminal-r12 { fill: #e6db74 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š1  def hello(name):β–Ž -β–Š2      x = 123β–Ž -β–Š3  whilenotFalse:                     β–Ž -β–Š4  print("hello " + name)           β–Ž -β–Š5  continueβ–Ž -β–Š6  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defhello(name):β–Ž +β–Š2      x = 123β–Ž +β–Š3  whilenotFalse:                     β–Ž +β–Š4  print("hello " + name)           β–Ž +β–Š5  continueβ–Ž +β–Š6  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[vscode_dark].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[vscode_dark].svg index 1185984e0c..43710bef03 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[vscode_dark].svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_themes[vscode_dark].svg @@ -19,83 +19,81 @@ font-weight: 700; } - .terminal-2571030914-matrix { + .terminal-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2571030914-title { + .terminal-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2571030914-r1 { fill: #121212 } -.terminal-2571030914-r2 { fill: #0178d4 } -.terminal-2571030914-r3 { fill: #c5c8c6 } -.terminal-2571030914-r4 { fill: #6e7681 } -.terminal-2571030914-r5 { fill: #569cd6 } -.terminal-2571030914-r6 { fill: #cccccc } -.terminal-2571030914-r7 { fill: #dcdcaa } -.terminal-2571030914-r8 { fill: #9cdcfe } -.terminal-2571030914-r9 { fill: #b5cea8 } -.terminal-2571030914-r10 { fill: #1e1e1e } -.terminal-2571030914-r11 { fill: #7daf9c } -.terminal-2571030914-r12 { fill: #4ec9b0 } -.terminal-2571030914-r13 { fill: #ce9178 } + .terminal-r1 { fill: #121212 } +.terminal-r2 { fill: #0178d4 } +.terminal-r3 { fill: #c5c8c6 } +.terminal-r4 { fill: #6e7681 } +.terminal-r5 { fill: #569cd6 } +.terminal-r6 { fill: #cccccc } +.terminal-r7 { fill: #dcdcaa } +.terminal-r8 { fill: #b5cea8 } +.terminal-r9 { fill: #1e1e1e } +.terminal-r10 { fill: #7daf9c } +.terminal-r11 { fill: #ce9178 } - + - + - + - + - + - + - + - + - + - TextAreaSnapshot + TextAreaSnapshot - - - - β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž -β–Š1  defhello(name):β–Ž -β–Š2  x = 123β–Ž -β–Š3  whilenotFalse:                     β–Ž -β–Š4  print("hello " + name)           β–Ž -β–Š5  continueβ–Ž -β–Š6  β–Ž -β–Šβ–Ž -β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defhello(name):β–Ž +β–Š2      x = 123β–Ž +β–Š3  whilenotFalse:                     β–Ž +β–Š4  print("hello " + name)           β–Ž +β–Š5  continueβ–Ž +β–Š6  β–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-False].svg new file mode 100644 index 0000000000..2d279d61b4 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-False].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-True].svg new file mode 100644 index 0000000000..e0f8a8b3ce --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press0-True].svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-False].svg new file mode 100644 index 0000000000..9c6b5694ff --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-False].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-True].svg new file mode 100644 index 0000000000..1234acf5d4 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press1-True].svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-False].svg new file mode 100644 index 0000000000..5b0ce68284 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-False].svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-True].svg new file mode 100644 index 0000000000..bf9eaee738 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press2-True].svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-False].svg new file mode 100644 index 0000000000..d325897ea5 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-False].svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-True].svg new file mode 100644 index 0000000000..7df6c464d8 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press3-True].svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-False].svg new file mode 100644 index 0000000000..893cab97af --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-False].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-True].svg new file mode 100644 index 0000000000..0ada6de74a --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press4-True].svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-False].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-False].svg new file mode 100644 index 0000000000..11ce1479c1 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-False].svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi", arg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name == 42:                 # Trailing commentβ–Ž +β–Š3  print('I think you māy have figured it out!')                    β–Ž +β–Š4  else:                                                                β–Ž +β–Š5  print('You need the one who is to come after me!')               β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-True].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-True].svg new file mode 100644 index 0000000000..716d4f6879 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_unicode_wide_syntax_highlighting[press5-True].svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + β–Šβ–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–Ž +β–Š1  defΕ«nicode_け(arg_āāけ="hi",      β–Ž +β–Šarg_b="bye"):   # Trailing commentβ–Ž +β–Š2  if unicode_ārg_name ==         β–Ž +β–Š42:                 # Trailing β–Ž +β–Šcommentβ–Ž +β–Š3  print('I think you māy β–Ž +β–Šhave figured it out!')             β–Ž +β–Š4  else:                          β–Ž +β–Š5  print('You need the one β–Ž +β–Šwho is to come after me!')         β–Ž +β–Šβ–Ž +β–Šβ–Ž +β–Šβ–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–β–Ž + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 2f3f458e43..1075651da8 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -1320,6 +1320,22 @@ def setup_selection(pilot): ) +@pytest.mark.skip("Paul Ollis: I think pilot.click does not do what is needed.") +def test_text_area_mouse_cursor_placement(snap_compare): + def setup_selection(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.theme = "css" + text_area.text = "Hello, world!\nNice to see you." + text_area.read_only = True + pilot.click(text_area, offset=(1, 4)) + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup_selection, + terminal_size=(30, 5), + ) + + @pytest.mark.syntax @pytest.mark.parametrize( "theme_name", [theme.name for theme in TextAreaTheme.builtin_themes()] @@ -1340,13 +1356,13 @@ def setup_theme(pilot): text_area = pilot.app.query_one(TextArea) text_area.load_text(text) text_area.language = "python" - text_area.selection = Selection((0, 1), (1, 9)) text_area.theme = theme_name assert snap_compare( SNAPSHOT_APPS_DIR / "text_area.py", run_before=setup_theme, terminal_size=(48, text.count("\n") + 4), + press=['right', 'shift+down'] + ['shift+right'] * 8, ) @@ -1369,6 +1385,168 @@ def test_text_area_line_number_start(snap_compare): ) +@pytest.mark.syntax +@pytest.mark.parametrize( + "press", + [ + # Just before horizontal scrolling should occur. + ['ctrl+right'] * 2 + ['right'] * 1, + + # Scroll right by a single character. + ['ctrl+right'] * 2 + ['right'] * 2, + + # Maximum first line scroll; i.e. cursor at end of first line. + ['ctrl+right'] * 5, + + # Maximum scroll; i.e. cursor at end of fifth line. + ['down'] * 5 + ['left'] + ['right'], + + # Cursor on the closing parenthesis on the fifth line. The opening + # parenthesis is scrolled off the LHSdt. of the TextArea. + ['down'] * 4 + ['ctrl+right'] * 12 + ['right'] * 2, + ], +) +def test_text_area_horizontal_scrolling(snap_compare, press): + text = """def a_function_with_a_long_name(long_argument_name): + if long_argument_name == 42: + print('I think you may have figured it out!') + else: + print('You need the one who is to come after me!')""" + + def setup(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.language = "python" + text_area.show_line_numbers = True + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup, + press=press, + terminal_size=(42, 10), + ) + + +@pytest.mark.syntax +@pytest.mark.parametrize( + "press", + [ + # Cursor on the closing parenthesis on the fourth line. Matching + # parenthesis just about to scroll off LHS. + ['down'] * 3 + ['ctrl+right'] * 6 + ['right'] * 2, + + # Cursor on the closing parenthesis on the fourth line. Matching + # parenthesis just scrolled off LHS. + ['down'] * 3 + ['ctrl+right'] * 6 + ['right'] * 3 + ["left"], + + # Cursor on the opening parenthesis on the 6th line. The closing + # parenthesis is scrolled off the RHS. + ['down'] * 5 + ['ctrl+right'] * 1, + ], +) +def test_text_area_horizontal_scrolling_cursor_matching(snap_compare, press): + text = """def a_function_with_a_long_name(long_argument_name): + if long_argument_name == 42: + print( + 'You have figured it out!') # 42 + else: + print( + 'You need the one who is to come after me!')""" + + def setup(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.language = "python" + text_area.show_line_numbers = True + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup, + press=press, + terminal_size=(42, 10), + ) + + +@pytest.mark.syntax +@pytest.mark.parametrize("soft_wrap", [False, True]) +@pytest.mark.parametrize( + "press", [ + # Move the cursor on to the 'Ε«' character. + ["ctrl+right"] * 1 + ["right"], + + # Move past the 'け' character then before it and the on to it. + ["ctrl+right"] * 2 + ["left"] * 2 + ["right"], + + # Move the cursor on to the opening parenthesis. + ["ctrl+right"] * 2, + + # Move the cursor on to the closing parenthesis. + ["ctrl+right"] * 10 + ["right"], + + # Move the cursor on to the second 'け' character. + ["ctrl+right"] * 4 + ["left"], + + # Move the cursor on to the character below the second 'け' character. + ["ctrl+right"] * 4 + ["left"] + ["down"], + ], +) +def test_text_area_unicode_wide_syntax_highlighting( + snap_compare, soft_wrap, press, +): + text = """def Ε«nicode_け(arg_āāけ="hi", arg_b="bye"): # Trailing comment + if unicode_ārg_name == 42: # Trailing comment + print('I think you māy have figured it out!') + else: + print('You need the one who is to come after me!')""" + + def setup(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.language = "python" + text_area.show_line_numbers = True + text_area.soft_wrap = soft_wrap + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup, + press=press, + terminal_size=(42, 14) if soft_wrap else (80, 10), + ) + + +@pytest.mark.syntax +@pytest.mark.skip("SVG rendering does not match terminal rendering") +@pytest.mark.parametrize( + "press", [ + ["ctrl+right"] * 1 + ["right"], + ["ctrl+right"] * 1 + ["right"] * 2, + ["ctrl+right"] * 1 + ["right"] * 3, + ["ctrl+right"] * 1 + ["right"] + ["left"], + ], +) +def test_text_area_unicode_zero_width_codepoint_syntax_highlighting( + snap_compare, press): + # The 'Ε«' in the function name below is actually formed by a 'u' followed + # by 2 u0304 characters, which are zero width, modifying codepoints. + text = """def uΜ„Μ„nicode_func(arg_āāā="hi", arg_b="bye"): # Trailing comment + if unicode_ārg_name == 42: # Trailing comment + print('I think you māy have figured it out!') + else: + print('You need the one who is to come after me!')""" + + def setup(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.language = "python" + text_area.show_line_numbers = True + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup, + press=press, + ) + + def test_digits(snap_compare) -> None: assert snap_compare(SNAPSHOT_APPS_DIR / "digits.py") @@ -1878,7 +2056,7 @@ class FormContainer(Vertical): DEFAULT_CSS = """ FormContainer { width: 50%; - border: blue; + border: blue; } """ @@ -2577,7 +2755,7 @@ class ThemeApp(App[None]): Screen { align: center middle; } - + Label { background: $panel; color: $text; @@ -2612,7 +2790,7 @@ class ThemeApp(App[None]): Screen { align: center middle; } - + Label { background: $custom-background; color: $custom-text; @@ -2787,7 +2965,7 @@ def test_position_absolute(snap_compare): class AbsoluteApp(App): CSS = """ - Screen { + Screen { align: center middle; .absolute { @@ -2802,7 +2980,7 @@ class AbsoluteApp(App): offset: 1 1; } .offset2 { - offset: 2 2; + offset: 2 2; } .offset3 { offset: 3 3; @@ -2840,7 +3018,7 @@ class GridOffsetApp(App): border: solid green; } - #six { + #six { offset: 0 10; background: blue; } @@ -3094,7 +3272,7 @@ class Test1(App): layout: horizontal; } - MainContainer { + MainContainer { width: 100%; height: 100%; background: red; @@ -3153,7 +3331,7 @@ class Sidebar(Vertical): height: auto; background: red; border: white; - } + } } """ @@ -3239,7 +3417,7 @@ class MyApp(App): height: auto; background: blue; } - Label { + Label { border: heavy red; text-align: left; } @@ -3292,12 +3470,12 @@ def test_collapsible_datatable(snap_compare): class MyApp(App): CSS = """ DataTable { - max-height: 1fr; - border: red; + max-height: 1fr; + border: red; } Collapsible { max-height: 50%; - + } """ @@ -3417,7 +3595,7 @@ def test_overflow(snap_compare): class OverflowApp(App): CSS = """ Label { - max-width: 100vw; + max-width: 100vw; } #label1 { # Overflow will be cropped @@ -3512,7 +3690,7 @@ def test_option_list_wrapping(snap_compare): class OLApp(App): CSS = """ - OptionList { + OptionList { width: 40; text-wrap: nowrap; text-overflow: ellipsis; @@ -3823,7 +4001,7 @@ def test_select_list_in_collapsible(snap_compare): class CustomWidget(Horizontal): DEFAULT_CSS = """ CustomWidget { - height: auto; + height: auto; } """ diff --git a/tests/text_area/test_edit_via_api.py b/tests/text_area/test_edit_via_api.py index e732680f0f..1b0ccf4cd0 100644 --- a/tests/text_area/test_edit_via_api.py +++ b/tests/text_area/test_edit_via_api.py @@ -115,9 +115,7 @@ async def test_insert_character_near_cursor_maintain_selection_offset( ], ) async def test_insert_newline_around_cursor_maintain_selection_offset( - cursor_location, - insert_location, - cursor_destination + cursor_location, insert_location, cursor_destination ): app = TextAreaApp() async with app.run_test(): @@ -187,6 +185,7 @@ async def test_insert_text_non_cursor_location_dont_maintain_offset(): assert result == EditResult( end_location=(4, 5), replaced_text="", + alt_dirty_line=(4, range(0, 5)), ) assert text_area.text == TEXT + "Hello" @@ -222,6 +221,7 @@ async def test_insert_multiline_text_maintain_offset(): assert result == EditResult( end_location=(3, 6), replaced_text="", + dirty_lines=range(2, 6), ) # The insert happens at the cursor (default location) @@ -252,6 +252,7 @@ async def test_replace_multiline_text(): assert result == EditResult( end_location=(3, 0), replaced_text=expected_replaced_text, + dirty_lines=range(1, 4), ) expected_content = """\ @@ -316,6 +317,7 @@ async def test_delete_within_line(): assert result == EditResult( end_location=(0, 6), replaced_text=" not", + alt_dirty_line=(0, range(7, 16)), ) expected_text = """\ @@ -368,6 +370,7 @@ async def test_delete_multiple_lines_selection_above(): assert result == EditResult( end_location=(1, 0), replaced_text=expected_replaced_text, + dirty_lines=range(1, 3), ) assert ( text_area.text @@ -447,7 +450,11 @@ async def test_insert_text_multiline_selection_top(select_from, select_to): end=(0, 2), ) - assert result == EditResult(end_location=(0, 5), replaced_text="AB") + assert result == EditResult( + end_location=(0, 5), + replaced_text="AB", + alt_dirty_line=(0, range(0, 8)), + ) # The edit range has grown from width 2 to width 5, so the # top line of the selection was adjusted (column+=3) such that the @@ -494,7 +501,11 @@ async def test_insert_text_multiline_selection_bottom(select_from, select_to): start=(2, 0), end=(2, 3), ) - assert result == EditResult(end_location=(2, 1), replaced_text="KLM") + assert result == EditResult( + end_location=(2, 1), + replaced_text="KLM", + alt_dirty_line=(2, range(0, 5)), + ) # The 'NO' from the selection is still available on the # bottom selection line, however the 'KLM' is replaced @@ -521,6 +532,7 @@ async def test_delete_fully_within_selection(): assert result == EditResult( replaced_text="45", end_location=(0, 4), + alt_dirty_line=(0, range(4, 10)), ) # We deleted 45, but the other characters are still available assert text_area.selected_text == "236" @@ -540,6 +552,7 @@ async def test_replace_fully_within_selection(): assert result == EditResult( replaced_text="234", end_location=(0, 4), + alt_dirty_line=(0, range(2, 10)), ) assert text_area.selected_text == "XX56" diff --git a/tests/text_area/test_languages.py b/tests/text_area/test_languages.py index f5f381354f..d1bdc54de6 100644 --- a/tests/text_area/test_languages.py +++ b/tests/text_area/test_languages.py @@ -86,10 +86,10 @@ async def test_update_highlight_query(): text_area = app.query_one(TextArea) # Before registering the language, we have highlights as expected. - assert len(text_area._highlights) > 0 + assert len(text_area._highlights[0]) > 0 # Overwriting the highlight query for Python... text_area.update_highlight_query("python", "") # We've overridden the highlight query with a blank one, so there are no highlights. - assert text_area._highlights == {} + assert len(text_area._highlights[0]) == 0