diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5db1a6405a..9fb99ff770 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
+- Fix priority bindings not appearing in footer when key clashes with focused widget https://github.com/Textualize/textual/pull/4342
+- Reverted auto-width change https://github.com/Textualize/textual/pull/4369
+
+### Changed
+
- Exceptions inside `Widget.compose` or workers weren't bubbling up in tests https://github.com/Textualize/textual/issues/4282
- Fixed `DataTable` scrolling issues by changing `max-height` back to 100% https://github.com/Textualize/textual/issues/4286
+- Fixed `Button` not rendering correctly with console markup https://github.com/Textualize/textual/issues/4328
### Added
- Added `Document.start` and `end` location properties for convenience https://github.com/Textualize/textual/pull/4267
+- Added support for JavaScript, Golang, Rust, Bash, Java and Kotlin to `TextArea` https://github.com/Textualize/textual/pull/4350
+- Added `inline` parameter to `run` and `run_async` to run app inline (under the prompt). https://github.com/Textualize/textual/pull/4343
+- Added `mouse` parameter to disable mouse support https://github.com/Textualize/textual/pull/4343
## [0.54.0] - 2024-03-26
diff --git a/docs/examples/how-to/inline01.py b/docs/examples/how-to/inline01.py
new file mode 100644
index 0000000000..65bd22f303
--- /dev/null
+++ b/docs/examples/how-to/inline01.py
@@ -0,0 +1,31 @@
+from datetime import datetime
+
+from textual.app import App, ComposeResult
+from textual.widgets import Digits
+
+
+class ClockApp(App):
+ CSS = """
+ Screen {
+ align: center middle;
+ }
+ #clock {
+ width: auto;
+ }
+ """
+
+ def compose(self) -> ComposeResult:
+ yield Digits("", id="clock")
+
+ def on_ready(self) -> None:
+ self.update_clock()
+ self.set_interval(1, self.update_clock)
+
+ def update_clock(self) -> None:
+ clock = datetime.now().time()
+ self.query_one(Digits).update(f"{clock:%T}")
+
+
+if __name__ == "__main__":
+ app = ClockApp()
+ app.run(inline=True) # (1)!
diff --git a/docs/examples/how-to/inline02.py b/docs/examples/how-to/inline02.py
new file mode 100644
index 0000000000..05b76e626a
--- /dev/null
+++ b/docs/examples/how-to/inline02.py
@@ -0,0 +1,38 @@
+from datetime import datetime
+
+from textual.app import App, ComposeResult
+from textual.widgets import Digits
+
+
+class ClockApp(App):
+ CSS = """
+ Screen {
+ align: center middle;
+ &:inline {
+ border: none;
+ height: 50vh;
+ Digits {
+ color: $success;
+ }
+ }
+ }
+ #clock {
+ width: auto;
+ }
+ """
+
+ def compose(self) -> ComposeResult:
+ yield Digits("", id="clock")
+
+ def on_ready(self) -> None:
+ self.update_clock()
+ self.set_interval(1, self.update_clock)
+
+ def update_clock(self) -> None:
+ clock = datetime.now().time()
+ self.query_one(Digits).update(f"{clock:%T}")
+
+
+if __name__ == "__main__":
+ app = ClockApp()
+ app.run(inline=True)
diff --git a/docs/examples/widgets/clock.py b/docs/examples/widgets/clock.py
index 6b18af3c85..10fb281d99 100644
--- a/docs/examples/widgets/clock.py
+++ b/docs/examples/widgets/clock.py
@@ -28,4 +28,4 @@ def update_clock(self) -> None:
if __name__ == "__main__":
app = ClockApp()
- app.run()
+ app.run(inline=True)
diff --git a/docs/guide/CSS.md b/docs/guide/CSS.md
index 5eccb2391a..daff1349a3 100644
--- a/docs/guide/CSS.md
+++ b/docs/guide/CSS.md
@@ -321,12 +321,13 @@ The `background: green` is only applied to the Button underneath the mouse curso
Here are some other pseudo classes:
+- `:blur` Matches widgets which *do not* have input focus.
+- `:dark` Matches widgets in dark mode (where `App.dark == True`).
- `:disabled` Matches widgets which are in a disabled state.
- `:enabled` Matches widgets which are in an enabled state.
-- `:focus` Matches widgets which have input focus.
-- `:blur` Matches widgets which *do not* have input focus.
- `:focus-within` Matches widgets with a focused child widget.
-- `:dark` Matches widgets in dark mode (where `App.dark == True`).
+- `:focus` Matches widgets which have input focus.
+- `:inline` Matches widgets when the app is running in inline mode.
- `:light` Matches widgets in dark mode (where `App.dark == False`).
## Combinators
diff --git a/docs/guide/animation.md b/docs/guide/animation.md
index 50bc87d35d..1bf55fb203 100644
--- a/docs/guide/animation.md
+++ b/docs/guide/animation.md
@@ -1,6 +1,6 @@
# Animation
-Ths chapter discusses how to use Textual's animation system to create visual effects such as movement, blending, and fading.
+This chapter discusses how to use Textual's animation system to create visual effects such as movement, blending, and fading.
## Animating styles
diff --git a/docs/guide/app.md b/docs/guide/app.md
index 5a59322802..7bc9e69e26 100644
--- a/docs/guide/app.md
+++ b/docs/guide/app.md
@@ -38,6 +38,14 @@ If you hit ++ctrl+c++ Textual will exit application mode and return you to the c
A side effect of application mode is that you may no longer be able to select and copy text in the usual way. Terminals typically offer a way to bypass this limit with a key modifier. On iTerm you can select text if you hold the ++option++ key. See the documentation for your terminal software for how to select text in application mode.
+#### Run inline
+
+!!! tip "Added in version 0.45.0"
+
+You can also run apps in _inline_ mode, which will cause the app to appear beneath the prompt (and won't go in to application mode).
+Inline apps are useful for tools that integrate closely with the typical workflow of a terminal.
+
+To run an app in inline mode set the `inline` parameter to `True` when you call [App.run()][textual.app.App.run]. See [Style Inline Apps](../how-to/style-inline-apps.md) for how to apply additional styles to inline apps.
## Events
diff --git a/docs/how-to/style-inline-apps.md b/docs/how-to/style-inline-apps.md
new file mode 100644
index 0000000000..a726f048c3
--- /dev/null
+++ b/docs/how-to/style-inline-apps.md
@@ -0,0 +1,37 @@
+# Style Inline Apps
+
+Version 0.55.0 of Textual added support for running apps *inline* (below the prompt).
+Running an inline app is as simple as adding `inline=True` to [`run()`][textual.app.App.run].
+
+
+
+Your apps will typically run inline without modification, but you may want to make some tweaks for inline mode, which you can do with a little CSS.
+This How-To will explain how.
+
+Let's look at an inline app.
+The following app displays the the current time (and keeps it up to date).
+
+```python hl_lines="31"
+--8<-- "docs/examples/how-to/inline01.py"
+```
+
+1. The `inline=True` runs the app inline.
+
+With Textual's default settings, this clock will be displayed in 5 lines; 3 for the digits and 2 for a top and bottom border.
+
+You can change the height or the border with CSS and the `:inline` pseudo-selector, which only matches rules in inline mode.
+Let's update this app to remove the default border, and increase the height:
+
+```python hl_lines="11-17"
+--8<-- "docs/examples/how-to/inline02.py"
+```
+
+The highlighted CSS targets online inline mode.
+By setting the `height` rule on Screen we can define how many lines the app should consume when it runs.
+Setting `border: none` removes the default border when running in inline mode.
+
+We've also added a rule to change the color of the clock when running inline.
+
+## Summary
+
+Most apps will not require modification to run inline, but if you want to tweak the height and border you can write CSS that targets inline mode with the `:inline` pseudo-selector.
diff --git a/docs/widgets/selection_list.md b/docs/widgets/selection_list.md
index 1ece006784..73de670754 100644
--- a/docs/widgets/selection_list.md
+++ b/docs/widgets/selection_list.md
@@ -30,8 +30,8 @@ my_selection_list: SelectionList[int] = SelectionList(*selections)
## Examples
A selection list is designed to be built up of single-line prompts (which
-can be [Rich renderables](../guide/widgets.md#rich-renderables)) and an
-associated unique value.
+can be [Rich `Text`](https://rich.readthedocs.io/en/stable/text.html)) and
+an associated unique value.
### Selections as tuples
diff --git a/examples/calculator.py b/examples/calculator.py
index 9c8f2f9e76..8f6442ebee 100644
--- a/examples/calculator.py
+++ b/examples/calculator.py
@@ -168,4 +168,4 @@ def pressed_equals(self) -> None:
if __name__ == "__main__":
- CalculatorApp().run()
+ CalculatorApp().run(inline=True)
diff --git a/examples/calculator.tcss b/examples/calculator.tcss
index f25b387fcd..180e3f8e0b 100644
--- a/examples/calculator.tcss
+++ b/examples/calculator.tcss
@@ -12,6 +12,10 @@ Screen {
min-height: 25;
min-width: 26;
height: 100%;
+
+ &:inline {
+ margin: 0 2;
+ }
}
Button {
diff --git a/examples/code_browser.tcss b/examples/code_browser.tcss
index 05928614b3..21fc7cad00 100644
--- a/examples/code_browser.tcss
+++ b/examples/code_browser.tcss
@@ -1,5 +1,8 @@
Screen {
background: $surface-darken-1;
+ &:inline {
+ height: 50vh;
+ }
}
#tree-view {
diff --git a/mkdocs-nav.yml b/mkdocs-nav.yml
index 7dd0863b43..4fa9912b41 100644
--- a/mkdocs-nav.yml
+++ b/mkdocs-nav.yml
@@ -223,6 +223,7 @@ nav:
- "how-to/design-a-layout.md"
- "how-to/package-with-hatch.md"
- "how-to/render-and-compose.md"
+ - "how-to/style-inline-apps.md"
- "FAQ.md"
- "roadmap.md"
- "Blog":
diff --git a/poetry.lock b/poetry.lock
index 94c07ff975..a313be29fa 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "aiohttp"
@@ -383,63 +383,63 @@ files = [
[[package]]
name = "coverage"
-version = "7.4.3"
+version = "7.4.4"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"},
- {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"},
- {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"},
- {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"},
- {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"},
- {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"},
- {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"},
- {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"},
- {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"},
- {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"},
- {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"},
- {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"},
- {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"},
- {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"},
- {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"},
- {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"},
- {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"},
- {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"},
- {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"},
- {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"},
- {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"},
- {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"},
- {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"},
- {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"},
- {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"},
- {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"},
- {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"},
- {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"},
- {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"},
- {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"},
- {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"},
- {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"},
- {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"},
- {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"},
- {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"},
- {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"},
- {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"},
- {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"},
- {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"},
- {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"},
- {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"},
- {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"},
- {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"},
- {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"},
- {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"},
- {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"},
- {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"},
- {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"},
- {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"},
- {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"},
- {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"},
- {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"},
+ {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
+ {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
+ {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
+ {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
+ {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
+ {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
+ {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
+ {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
+ {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
+ {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
+ {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
+ {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
+ {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
+ {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
+ {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
+ {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
+ {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
+ {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
+ {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
+ {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
+ {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
+ {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
+ {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
+ {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
+ {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
+ {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
+ {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
+ {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
+ {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
+ {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
+ {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
+ {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
+ {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
+ {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
+ {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
+ {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
+ {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
+ {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
+ {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
+ {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
+ {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
+ {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
+ {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
+ {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
+ {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
+ {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
+ {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
+ {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
+ {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
+ {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
+ {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
+ {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
]
[package.extras]
@@ -472,18 +472,18 @@ test = ["pytest (>=6)"]
[[package]]
name = "filelock"
-version = "3.13.1"
+version = "3.13.3"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
- {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
- {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
+ {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"},
+ {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"},
]
[package.extras]
-docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
@@ -716,22 +716,22 @@ files = [
[[package]]
name = "importlib-metadata"
-version = "7.0.1"
+version = "7.1.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
- {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
+ {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
+ {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
+testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "iniconfig"
@@ -783,13 +783,13 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "markdown"
-version = "3.5.2"
+version = "3.6"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.8"
files = [
- {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
- {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
+ {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"},
+ {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"},
]
[package.dependencies]
@@ -997,13 +997,13 @@ mkdocs = "*"
[[package]]
name = "mkdocs-material"
-version = "9.5.12"
+version = "9.5.15"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.12-py3-none-any.whl", hash = "sha256:d6f0c269f015e48c76291cdc79efb70f7b33bbbf42d649cfe475522ebee61b1f"},
- {file = "mkdocs_material-9.5.12.tar.gz", hash = "sha256:5f69cef6a8aaa4050b812f72b1094fda3d079b9a51cf27a247244c03ec455e97"},
+ {file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"},
+ {file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"},
]
[package.dependencies]
@@ -1159,7 +1159,7 @@ files = [
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
- {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"},
+ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
]
[[package]]
@@ -1263,38 +1263,38 @@ files = [
[[package]]
name = "mypy"
-version = "1.8.0"
+version = "1.9.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
- {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
- {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
- {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
- {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
- {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
- {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
- {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
- {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
- {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
- {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
- {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
- {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
- {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
- {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
- {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
- {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
- {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
- {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
- {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
- {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
- {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
- {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
- {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
- {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
- {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
- {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
+ {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"},
+ {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"},
+ {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"},
+ {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"},
+ {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"},
+ {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"},
+ {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"},
+ {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"},
+ {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"},
+ {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"},
+ {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"},
+ {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"},
+ {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"},
+ {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"},
+ {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"},
+ {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"},
+ {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"},
+ {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"},
+ {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"},
+ {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"},
+ {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"},
+ {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"},
+ {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"},
+ {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"},
+ {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"},
+ {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"},
+ {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"},
]
[package.dependencies]
@@ -1335,13 +1335,13 @@ setuptools = "*"
[[package]]
name = "packaging"
-version = "23.2"
+version = "24.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
- {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
- {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
@@ -1430,13 +1430,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymdown-extensions"
-version = "10.7"
+version = "10.7.1"
description = "Extension pack for Python Markdown."
optional = false
python-versions = ">=3.8"
files = [
- {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
- {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
+ {file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"},
+ {file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"},
]
[package.dependencies]
@@ -1470,13 +1470,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
[[package]]
name = "pytest-asyncio"
-version = "0.23.5"
+version = "0.23.6"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"},
- {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"},
+ {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"},
+ {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"},
]
[package.dependencies]
@@ -1525,13 +1525,13 @@ textual = ">=0.28.0"
[[package]]
name = "python-dateutil"
-version = "2.9.0"
+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"
files = [
- {file = "python-dateutil-2.9.0.tar.gz", hash = "sha256:78e73e19c63f5b20ffa567001531680d939dc042bf7850431877645523c66709"},
- {file = "python_dateutil-2.9.0-py2.py3-none-any.whl", hash = "sha256:cbf2f1da5e6083ac2fbfd4da39a25f34312230110440f424a14c7558bb85d82e"},
+ {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"},
]
[package.dependencies]
@@ -1560,6 +1560,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -1567,8 +1568,15 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -1585,6 +1593,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -1592,6 +1601,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -1772,18 +1782,18 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "setuptools"
-version = "69.1.1"
+version = "69.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"},
- {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"},
+ {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"},
+ {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
@@ -2046,13 +2056,13 @@ files = [
[[package]]
name = "types-tree-sitter"
-version = "0.20.1.20240106"
+version = "0.20.1.20240311"
description = "Typing stubs for tree-sitter"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-tree-sitter-0.20.1.20240106.tar.gz", hash = "sha256:b0866a74942af5e223ceda9d1665befab9d55e0ccaa6704215efeaa2b02b0ca6"},
- {file = "types_tree_sitter-0.20.1.20240106-py3-none-any.whl", hash = "sha256:3b38b2500d3235f07644bc7148c5c9dbc929fdcb5d10d36709601028c9e0ebe2"},
+ {file = "types-tree-sitter-0.20.1.20240311.tar.gz", hash = "sha256:a046e3da64efa68ac23dfeb7a6fbe10787294e802c5cad46d8b6daf765f5e409"},
+ {file = "types_tree_sitter-0.20.1.20240311-py3-none-any.whl", hash = "sha256:fb7babaad37c907fa11904f33bae7f8a99b1a288b3c3099ed01f527fc0073069"},
]
[[package]]
@@ -2288,23 +2298,23 @@ multidict = ">=4.0"
[[package]]
name = "zipp"
-version = "3.17.0"
+version = "3.18.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
- {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
- {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
+ {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
+ {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[extras]
-syntax = ["tree-sitter", "tree_sitter_languages"]
+syntax = ["tree-sitter", "tree-sitter-languages"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "014186a223d4236fb0ace86bef24fb030d6921cf714a8b4d2b889ba066a26375"
+content-hash = "4d935ffc4d465a2e51881f971fe3b9b62b9a1a8462ae511a512b5d7fae06a733"
diff --git a/pyproject.toml b/pyproject.toml
index 630cc27599..81f6ecfa7f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ rich = ">=13.3.3"
#rich = {path="../rich", develop=true}
typing-extensions = "^4.4.0"
tree-sitter = { version = "^0.20.1", optional = true }
-tree_sitter_languages = { version = ">=1.7.0", optional = true }
+tree-sitter-languages = { version = "1.10.2", optional = true }
[tool.poetry.extras]
syntax = ["tree-sitter", "tree_sitter_languages"]
diff --git a/src/textual/_animator.py b/src/textual/_animator.py
index 27d6c96ce5..21234fee8b 100644
--- a/src/textual/_animator.py
+++ b/src/textual/_animator.py
@@ -191,7 +191,7 @@ def __call__(
attribute: Name of the attribute to animate.
value: The value to animate to.
final_value: The final value of the animation. Defaults to `value` if not set.
- duration: The duration of the animate.
+ duration: The duration (in seconds) of the animation.
speed: The speed of the animation.
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py
index 6b786e8b26..d9047e3fc3 100644
--- a/src/textual/_compositor.py
+++ b/src/textual/_compositor.py
@@ -144,6 +144,48 @@ def __rich_repr__(self) -> rich.repr.Result:
yield self.region
+@rich.repr.auto(angular=True)
+class InlineUpdate(CompositorUpdate):
+ """A renderable to write an inline update."""
+
+ def __init__(self, strips: list[Strip]) -> None:
+ self.strips = strips
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ new_line = Segment.line()
+ for last, line in loop_last(self.strips):
+ yield from line
+ if not last:
+ yield new_line
+
+ def render_segments(self, console: Console) -> str:
+ """Render the update to raw data, suitable for writing to terminal.
+
+ Args:
+ console: Console instance.
+
+ Returns:
+ Raw data with escape sequences.
+ """
+ sequences: list[str] = []
+ append = sequences.append
+ for last, strip in loop_last(self.strips):
+ append(strip.render(console))
+ if not last:
+ append("\n")
+ append("\n\x1b[J") # Clear down
+ if len(self.strips) > 1:
+ append(
+ f"\x1b[{len(self.strips)}A\r"
+ ) # Move cursor back to original position
+ else:
+ append("\r")
+ append("\x1b[6n") # Query new cursor position
+ return "".join(sequences)
+
+
@rich.repr.auto(angular=True)
class ChopsUpdate(CompositorUpdate):
"""A renderable that applies updated spans to the screen."""
@@ -953,7 +995,7 @@ def render_update(
"""Render an update renderable.
Args:
- full: Enable full update, or `False` for a partial update.
+ screen_stack: Screen stack list. Defaults to None.
Returns:
A renderable for the update, or `None` if no update was required.
@@ -966,6 +1008,21 @@ def render_update(
else:
return self.render_partial_update()
+ def render_inline(
+ self, size: Size, screen_stack: list[Screen] | None = None
+ ) -> RenderableType:
+ """Render an inline update.
+
+ Args:
+ size: Inline size.
+ screen_stack: Screen stack list. Defaults to None.
+
+ Returns:
+ A renderable.
+ """
+ visible_screen_stack.set([] if screen_stack is None else screen_stack)
+ return InlineUpdate(self.render_strips(size))
+
def render_full_update(self) -> LayoutUpdate:
"""Render a full update.
@@ -999,14 +1056,19 @@ def render_partial_update(self) -> ChopsUpdate | None:
chop_ends = [cut_set[1:] for cut_set in self.cuts]
return ChopsUpdate(chops, spans, chop_ends)
- def render_strips(self) -> list[Strip]:
+ def render_strips(self, size: Size | None = None) -> list[Strip]:
"""Render to a list of strips.
+ Args:
+ size: Size of render.
+
Returns:
A list of strips with the screen content.
"""
- chops = self._render_chops(self.size.region, lambda y: True)
- render_strips = [Strip.join(chop.values()) for chop in chops]
+ if size is None:
+ size = self.size
+ chops = self._render_chops(size.region, lambda y: True)
+ render_strips = [Strip.join(chop.values()) for chop in chops[: size.height]]
return render_strips
def _render_chops(
diff --git a/src/textual/_layout.py b/src/textual/_layout.py
index 8048a0f303..312f8c9fe5 100644
--- a/src/textual/_layout.py
+++ b/src/textual/_layout.py
@@ -160,7 +160,7 @@ def get_content_width(self, widget: Widget, container: Size, viewport: Size) ->
if not widget._nodes:
width = 0
else:
- arrangement = widget._arrange(Size(container.width, 0))
+ arrangement = widget._arrange(Size(0, 0))
return arrangement.total_region.right
return width
diff --git a/src/textual/_resolve.py b/src/textual/_resolve.py
index eecd99dcf1..647ee53f55 100644
--- a/src/textual/_resolve.py
+++ b/src/textual/_resolve.py
@@ -86,7 +86,7 @@ def resolve_fraction_unit(
remaining_space: Fraction,
resolve_dimension: Literal["width", "height"] = "width",
) -> Fraction:
- """Calculate the fraction
+ """Calculate the fraction.
Args:
widget_styles: Styles for widgets with fraction units.
diff --git a/src/textual/_text_area_theme.py b/src/textual/_text_area_theme.py
index 5a435b3287..ad2e607f84 100644
--- a/src/textual/_text_area_theme.py
+++ b/src/textual/_text_area_theme.py
@@ -222,6 +222,7 @@ def builtin_themes(cls) -> list[TextAreaTheme]:
"method": Style(color="#A6E22E"),
"method.call": Style(color="#A6E22E"),
"boolean": Style(color="#66D9EF", italic=True),
+ "constant.builtin": Style(color="#66D9EF", italic=True),
"json.null": Style(color="#66D9EF", italic=True),
"regex.punctuation.bracket": Style(color="#F92672"),
"regex.operator": Style(color="#F92672"),
@@ -271,6 +272,7 @@ def builtin_themes(cls) -> list[TextAreaTheme]:
"method": Style(color="#50fa7b"),
"method.call": Style(color="#50fa7b"),
"boolean": Style(color="#bd93f9"),
+ "constant.builtin": Style(color="#bd93f9"),
"json.null": Style(color="#bd93f9"),
"regex.punctuation.bracket": Style(color="#ff79c6"),
"regex.operator": Style(color="#ff79c6"),
@@ -320,6 +322,7 @@ def builtin_themes(cls) -> list[TextAreaTheme]:
"method": Style(color="#4EC9B0"),
"method.call": Style(color="#4EC9B0"),
"boolean": Style(color="#7DAF9C"),
+ "constant.builtin": Style(color="#7DAF9C"),
"json.null": Style(color="#7DAF9C"),
"tag": Style(color="#EFCB43"),
"yaml.field": Style(color="#569cd6", bold=True),
@@ -365,6 +368,7 @@ def builtin_themes(cls) -> list[TextAreaTheme]:
"function": Style(color="#6639BB"),
"method": Style(color="#6639BB"),
"boolean": Style(color="#7DAF9C"),
+ "constant.builtin": Style(color="#7DAF9C"),
"tag": Style(color="#6639BB"),
"yaml.field": Style(color="#6639BB"),
"json.label": Style(color="#6639BB"),
diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py
index 78a4fce4ac..fc84391f39 100644
--- a/src/textual/_xterm_parser.py
+++ b/src/textual/_xterm_parser.py
@@ -21,6 +21,8 @@
"^" + re.escape("\x1b[") + r"\?(?P\d+);(?P\d)\$y"
)
+_re_cursor_position = re.compile(r"\x1b\[(?P\d+);(?P\d+)R")
+
BRACKETED_PASTE_START: Final[str] = "\x1b[200~"
"""Sequence received when a bracketed paste event starts."""
BRACKETED_PASTE_END: Final[str] = "\x1b[201~"
@@ -235,8 +237,7 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None:
if key_events:
break
# Or a mouse event?
- mouse_match = _re_mouse_event.match(sequence)
- if mouse_match is not None:
+ if (mouse_match := _re_mouse_event.match(sequence)) is not None:
mouse_code = mouse_match.group(0)
event = self.parse_mouse_code(mouse_code)
if event:
@@ -245,14 +246,28 @@ def reissue_sequence_as_keys(reissue_sequence: str) -> None:
# Or a mode report?
# (i.e. the terminal saying it supports a mode we requested)
- mode_report_match = _re_terminal_mode_response.match(sequence)
- if mode_report_match is not None:
+ if (
+ mode_report_match := _re_terminal_mode_response.match(
+ sequence
+ )
+ ) is not None:
if (
mode_report_match["mode_id"] == "2026"
and int(mode_report_match["setting_parameter"]) > 0
):
on_token(messages.TerminalSupportsSynchronizedOutput())
break
+
+ # Or a cursor position query?
+ if (
+ cursor_position_match := _re_cursor_position.match(sequence)
+ ) is not None:
+ row, column = cursor_position_match.groups()
+ on_token(
+ events.CursorPosition(x=int(column) - 1, y=int(row) - 1)
+ )
+ break
+
else:
if not bracketed_paste:
for event in sequence_to_key_events(character):
diff --git a/src/textual/app.py b/src/textual/app.py
index 15811232c9..d7966761db 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -45,7 +45,6 @@
Type,
TypeVar,
Union,
- cast,
overload,
)
from weakref import WeakKeyDictionary, WeakSet
@@ -92,7 +91,6 @@
from .design import ColorSystem
from .dom import DOMNode, NoScreen
from .driver import Driver
-from .drivers.headless_driver import HeadlessDriver
from .errors import NoWidget
from .features import FeatureFlag, parse_features
from .file_monitor import FileMonitor
@@ -476,6 +474,9 @@ def __init__(
self._mouse_down_widget: Widget | None = None
"""The widget that was most recently mouse downed (used to create click events)."""
+ self._previous_cursor_position = Offset(0, 0)
+ """The previous cursor position"""
+
self.cursor_position = Offset(0, 0)
"""The position of the terminal cursor in screen-space.
@@ -741,7 +742,7 @@ def animate(
attribute: Name of the attribute to animate.
value: The value to animate to.
final_value: The final value of the animation.
- duration: The duration of the animate.
+ duration: The duration (in seconds) of the animation.
speed: The speed of the animation.
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
@@ -779,12 +780,17 @@ def debug(self) -> bool:
@property
def is_headless(self) -> bool:
- """Is the driver running in 'headless' mode?
+ """Is the app running in 'headless' mode?
Headless mode is used when running tests with [run_test][textual.app.App.run_test].
"""
return False if self._driver is None else self._driver.is_headless
+ @property
+ def is_inline(self) -> bool:
+ """Is the app running in 'inline' mode?"""
+ return False if self._driver is None else self._driver.is_inline
+
@property
def screen_stack(self) -> Sequence[Screen[Any]]:
"""A snapshot of the current screen stack.
@@ -855,15 +861,21 @@ def namespace_bindings(self) -> dict[str, tuple[DOMNode, Binding]]:
This property may be used to inspect current bindings.
Returns:
- A mapping of keys onto pairs of nodes and bindings.
+ A map of keys to a tuple containing the DOMNode and Binding that key corresponds to.
"""
- namespace_binding_map: dict[str, tuple[DOMNode, Binding]] = {}
+ bindings_map: dict[str, tuple[DOMNode, Binding]] = {}
for namespace, bindings in reversed(self._binding_chain):
for key, binding in bindings.keys.items():
- namespace_binding_map[key] = (namespace, binding)
+ existing_key_and_binding = bindings_map.get(key)
+ if existing_key_and_binding:
+ _, existing_binding = existing_key_and_binding
+ if binding.priority and not existing_binding.priority:
+ bindings_map[key] = (namespace, binding)
+ else:
+ bindings_map[key] = (namespace, binding)
- return namespace_binding_map
+ return bindings_map
def _set_active(self) -> None:
"""Set this app to be the currently active app."""
@@ -979,6 +991,7 @@ def __rich_repr__(self) -> rich.repr.Result:
@property
def animator(self) -> Animator:
+ """The animator object."""
return self._animator
@property
@@ -1444,6 +1457,9 @@ async def run_async(
self,
*,
headless: bool = False,
+ inline: bool = False,
+ inline_no_clear: bool = False,
+ mouse: bool = False,
size: tuple[int, int] | None = None,
auto_pilot: AutopilotCallbackType | None = None,
) -> ReturnType | None:
@@ -1451,6 +1467,9 @@ async def run_async(
Args:
headless: Run in headless mode (no output).
+ inline: Run the app inline (under the prompt).
+ inline_no_clear: Don't clear the app output when exiting an inline app.
+ mouse: Enable mouse support.
size: Force terminal size to `(WIDTH, HEIGHT)`,
or None to auto-detect.
auto_pilot: An auto pilot coroutine.
@@ -1501,6 +1520,9 @@ async def run_auto_pilot(
await app._process_messages(
ready_callback=None if auto_pilot is None else app_ready,
headless=headless,
+ inline=inline,
+ inline_no_clear=inline_no_clear,
+ mouse=mouse,
terminal_size=size,
)
finally:
@@ -1516,6 +1538,9 @@ def run(
self,
*,
headless: bool = False,
+ inline: bool = False,
+ inline_no_clear: bool = False,
+ mouse: bool = True,
size: tuple[int, int] | None = None,
auto_pilot: AutopilotCallbackType | None = None,
) -> ReturnType | None:
@@ -1523,6 +1548,9 @@ def run(
Args:
headless: Run in headless mode (no output).
+ inline: Run the app inline (under the prompt).
+ inline_no_clear: Don't clear the app output when exiting an inline app.
+ mouse: Enable mouse support.
size: Force terminal size to `(WIDTH, HEIGHT)`,
or None to auto-detect.
auto_pilot: An auto pilot coroutine.
@@ -1538,6 +1566,9 @@ async def run_app() -> None:
try:
await self.run_async(
headless=headless,
+ inline=inline,
+ inline_no_clear=inline_no_clear,
+ mouse=mouse,
size=size,
auto_pilot=auto_pilot,
)
@@ -2297,6 +2328,9 @@ async def _process_messages(
self,
ready_callback: CallbackType | None = None,
headless: bool = False,
+ inline: bool = False,
+ inline_no_clear: bool = False,
+ mouse: bool = True,
terminal_size: tuple[int, int] | None = None,
message_hook: Callable[[Message], None] | None = None,
) -> None:
@@ -2314,7 +2348,6 @@ async def _process_messages(
self.log.system("---")
- self.log.system(driver=self.driver_class)
self.log.system(loop=asyncio.get_running_loop())
self.log.system(features=self.features)
if constants.LOG_FILE is not None:
@@ -2406,15 +2439,26 @@ async def invoke_ready_callback() -> None:
await self._dispatch_message(load_event)
driver: Driver
- driver_class = cast(
- "type[Driver]",
- HeadlessDriver if headless else self.driver_class,
- )
+
+ driver_class: type[Driver]
+ if headless:
+ from .drivers.headless_driver import HeadlessDriver
+
+ driver_class = HeadlessDriver
+ elif inline:
+ from .drivers.linux_inline_driver import LinuxInlineDriver
+
+ driver_class = LinuxInlineDriver
+ else:
+ driver_class = self.driver_class
+
driver = self._driver = driver_class(
self,
debug=constants.DEBUG,
+ mouse=mouse,
size=terminal_size,
)
+ self.log(driver=driver)
if not self._exit:
driver.start_application_mode()
@@ -2424,6 +2468,15 @@ async def invoke_ready_callback() -> None:
await run_process_messages()
finally:
+ if self._driver.is_inline:
+ cursor_x, cursor_y = self._previous_cursor_position
+ self._driver.write(
+ Control.move(-cursor_x, -cursor_y).segment.text
+ )
+ if inline_no_clear:
+ console = Console()
+ console.print(self.screen._compositor)
+ console.print()
driver.stop_application_mode()
except Exception as error:
self._handle_exception(error)
@@ -2737,11 +2790,16 @@ def _display(self, screen: Screen, renderable: RenderableType | None) -> None:
try:
try:
if isinstance(renderable, CompositorUpdate):
+ cursor_x, cursor_y = self._previous_cursor_position
+ terminal_sequence = Control.move(
+ -cursor_x, -cursor_y
+ ).segment.text
cursor_x, cursor_y = self.cursor_position
- terminal_sequence = renderable.render_segments(console)
- terminal_sequence += Control.move_to(
+ terminal_sequence += renderable.render_segments(console)
+ terminal_sequence += Control.move(
cursor_x, cursor_y
).segment.text
+ self._previous_cursor_position = self.cursor_position
else:
segments = console.render(renderable)
terminal_sequence = console._render_buffer(segments)
@@ -3387,7 +3445,7 @@ def notify(
message: The message for the notification.
title: The title for the notification.
severity: The severity of the notification.
- timeout: The timeout for the notification.
+ timeout: The timeout (in seconds) for the notification.
The `notify` method is used to create an application-wide
notification, shown in a [`Toast`][textual.widgets._toast.Toast],
diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py
index 52c13cb067..27c662dde9 100644
--- a/src/textual/css/constants.py
+++ b/src/textual/css/constants.py
@@ -68,6 +68,7 @@
"focus-within",
"focus",
"hover",
+ "inline",
"light",
}
VALID_OVERLAY: Final = {"none", "screen"}
diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py
index 8bc14d49c9..6566d61847 100644
--- a/src/textual/css/styles.py
+++ b/src/textual/css/styles.py
@@ -1152,7 +1152,7 @@ def animate(
attribute: Name of the attribute to animate.
value: The value to animate to.
final_value: The final value of the animation. Defaults to `value` if not set.
- duration: The duration of the animate.
+ duration: The duration (in seconds) of the animation.
speed: The speed of the animation.
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
diff --git a/src/textual/demo.tcss b/src/textual/demo.tcss
index 2e9d54e25b..fc5f45b23f 100644
--- a/src/textual/demo.tcss
+++ b/src/textual/demo.tcss
@@ -5,6 +5,9 @@
Screen {
layers: base overlay notes notifications;
overflow: hidden;
+ &:inline {
+ height: 50vh;
+ }
}
diff --git a/src/textual/document/_languages.py b/src/textual/document/_languages.py
index a33f7544e8..fb313cf7ca 100644
--- a/src/textual/document/_languages.py
+++ b/src/textual/document/_languages.py
@@ -1,13 +1,19 @@
BUILTIN_LANGUAGES = sorted(
[
- "markdown",
- "yaml",
- "sql",
+ "bash",
"css",
+ "go",
"html",
+ "java",
+ "javascript",
"json",
+ "kotlin",
+ "markdown",
"python",
+ "rust",
"regex",
+ "sql",
"toml",
+ "yaml",
]
)
diff --git a/src/textual/driver.py b/src/textual/driver.py
index c70edf9588..f0ad89c98b 100644
--- a/src/textual/driver.py
+++ b/src/textual/driver.py
@@ -20,6 +20,7 @@ def __init__(
app: App,
*,
debug: bool = False,
+ mouse: bool = True,
size: tuple[int, int] | None = None,
) -> None:
"""Initialize a driver.
@@ -27,22 +28,30 @@ def __init__(
Args:
app: The App instance.
debug: Enable debug mode.
+ mouse: Enable mouse support,
size: Initial size of the terminal or `None` to detect.
"""
self._app = app
self._debug = debug
+ self._mouse = mouse
self._size = size
self._loop = asyncio.get_running_loop()
self._down_buttons: list[int] = []
self._last_move_event: events.MouseMove | None = None
self._auto_restart = True
"""Should the application auto-restart (where appropriate)?"""
+ self.cursor_origin: tuple[int, int] | None = None
@property
def is_headless(self) -> bool:
"""Is the driver 'headless' (no output)?"""
return False
+ @property
+ def is_inline(self) -> bool:
+ """Is the driver 'inline' (not full-screen)?"""
+ return False
+
@property
def can_suspend(self) -> bool:
"""Can this driver be suspended?"""
@@ -67,6 +76,17 @@ def process_event(self, event: events.Event) -> None:
# NOTE: This runs in a thread.
# Avoid calling methods on the app.
event.set_sender(self._app)
+ if self.cursor_origin is None:
+ offset_x = 0
+ offset_y = 0
+ else:
+ offset_x, offset_y = self.cursor_origin
+ if isinstance(event, events.MouseEvent):
+ event.x -= offset_x
+ event.y -= offset_y
+ event.screen_x -= offset_x
+ event.screen_y -= offset_y
+
if isinstance(event, events.MouseDown):
if event.button:
self._down_buttons.append(event.button)
@@ -79,6 +99,7 @@ def process_event(self, event: events.Event) -> None:
and not event.button
and self._last_move_event is not None
):
+
# Deduplicate self._down_buttons while preserving order.
buttons = list(dict.fromkeys(self._down_buttons).keys())
self._down_buttons.clear()
diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py
index 3e7958837c..8b38190dc7 100644
--- a/src/textual/drivers/linux_driver.py
+++ b/src/textual/drivers/linux_driver.py
@@ -32,6 +32,7 @@ def __init__(
app: App,
*,
debug: bool = False,
+ mouse: bool = True,
size: tuple[int, int] | None = None,
) -> None:
"""Initialize Linux driver.
@@ -39,9 +40,10 @@ def __init__(
Args:
app: The App instance.
debug: Enable debug mode.
+ mouse: Enable mouse support.
size: Initial size of the terminal or `None` to detect.
"""
- super().__init__(app, debug=debug, size=size)
+ super().__init__(app, debug=debug, mouse=mouse, size=size)
self._file = sys.__stderr__
self.fileno = sys.__stdin__.fileno()
self.attrs_before: list[Any] | None = None
@@ -111,6 +113,8 @@ def _get_terminal_size(self) -> tuple[int, int]:
def _enable_mouse_support(self) -> None:
"""Enable reporting of mouse events."""
+ if not self._mouse:
+ return
write = self.write
write("\x1b[?1000h") # SET_VT200_MOUSE
write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE
@@ -133,6 +137,8 @@ def _disable_bracketed_paste(self) -> None:
def _disable_mouse_support(self) -> None:
"""Disable reporting of mouse events."""
+ if not self._mouse:
+ return
write = self.write
write("\x1b[?1000l") #
write("\x1b[?1003l") #
diff --git a/src/textual/drivers/linux_inline_driver.py b/src/textual/drivers/linux_inline_driver.py
new file mode 100644
index 0000000000..c43ff33e54
--- /dev/null
+++ b/src/textual/drivers/linux_inline_driver.py
@@ -0,0 +1,274 @@
+from __future__ import annotations
+
+import asyncio
+import os
+import selectors
+import signal
+import sys
+import termios
+import tty
+from codecs import getincrementaldecoder
+from threading import Event, Thread
+from typing import TYPE_CHECKING, Any
+
+import rich.repr
+
+from .. import events
+from .._xterm_parser import XTermParser
+from ..driver import Driver
+from ..geometry import Size
+
+if TYPE_CHECKING:
+ from ..app import App
+
+
+@rich.repr.auto(angular=True)
+class LinuxInlineDriver(Driver):
+
+ def __init__(
+ self,
+ app: App,
+ *,
+ debug: bool = False,
+ mouse: bool = True,
+ size: tuple[int, int] | None = None,
+ ):
+ super().__init__(app, debug=debug, mouse=mouse, size=size)
+ self._file = sys.__stderr__
+ self.fileno = sys.__stdin__.fileno()
+ self.attrs_before: list[Any] | None = None
+ self.exit_event = Event()
+
+ def __rich_repr__(self) -> rich.repr.Result:
+ yield self._app
+
+ @property
+ def is_inline(self) -> bool:
+ return True
+
+ def _enable_bracketed_paste(self) -> None:
+ """Enable bracketed paste mode."""
+ self.write("\x1b[?2004h")
+
+ def _disable_bracketed_paste(self) -> None:
+ """Disable bracketed paste mode."""
+ self.write("\x1b[?2004l")
+
+ def _get_terminal_size(self) -> tuple[int, int]:
+ """Detect the terminal size.
+
+ Returns:
+ The size of the terminal as a tuple of (WIDTH, HEIGHT).
+ """
+ width: int | None = 80
+ height: int | None = 25
+ import shutil
+
+ try:
+ width, height = shutil.get_terminal_size()
+ except (AttributeError, ValueError, OSError):
+ try:
+ width, height = shutil.get_terminal_size()
+ except (AttributeError, ValueError, OSError):
+ pass
+ width = width or 80
+ height = height or 25
+ return width, height
+
+ def _enable_mouse_support(self) -> None:
+ """Enable reporting of mouse events."""
+ if not self._mouse:
+ return
+ write = self.write
+ write("\x1b[?1000h") # SET_VT200_MOUSE
+ write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE
+ write("\x1b[?1015h") # SET_VT200_HIGHLIGHT_MOUSE
+ write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE
+
+ # write("\x1b[?1007h")
+ self.flush()
+
+ def _disable_mouse_support(self) -> None:
+ """Disable reporting of mouse events."""
+ if not self._mouse:
+ return
+ write = self.write
+ write("\x1b[?1000l") #
+ write("\x1b[?1003l") #
+ write("\x1b[?1015l")
+ write("\x1b[?1006l")
+ self.flush()
+
+ def write(self, data: str) -> None:
+ self._file.write(data)
+
+ def _run_input_thread(self) -> None:
+ """
+ Key thread target that wraps run_input_thread() to die gracefully if it raises
+ an exception
+ """
+ try:
+ self.run_input_thread()
+ except BaseException as error:
+ import rich.traceback
+
+ self._app.call_later(
+ self._app.panic,
+ rich.traceback.Traceback(),
+ )
+
+ def run_input_thread(self) -> None:
+ """Wait for input and dispatch events."""
+ selector = selectors.SelectSelector()
+ selector.register(self.fileno, selectors.EVENT_READ)
+
+ fileno = self.fileno
+ EVENT_READ = selectors.EVENT_READ
+
+ def more_data() -> bool:
+ """Check if there is more data to parse."""
+
+ for _key, events in selector.select(0.01):
+ if events & EVENT_READ:
+ return True
+ return False
+
+ parser = XTermParser(more_data, self._debug)
+ feed = parser.feed
+
+ utf8_decoder = getincrementaldecoder("utf-8")().decode
+ decode = utf8_decoder
+ read = os.read
+
+ try:
+ while not self.exit_event.is_set():
+ selector_events = selector.select(0.1)
+ for _selector_key, mask in selector_events:
+ if mask & EVENT_READ:
+ unicode_data = decode(
+ read(fileno, 1024), final=self.exit_event.is_set()
+ )
+ for event in feed(unicode_data):
+ if isinstance(event, events.CursorPosition):
+ self.cursor_origin = (event.x, event.y)
+ else:
+ self.process_event(event)
+ finally:
+ selector.close()
+
+ def start_application_mode(self) -> None:
+
+ loop = asyncio.get_running_loop()
+
+ def send_size_event() -> None:
+ terminal_size = self._get_terminal_size()
+ width, height = terminal_size
+ textual_size = Size(width, height)
+ event = events.Resize(textual_size, textual_size)
+ asyncio.run_coroutine_threadsafe(
+ self._app._post_message(event),
+ loop=loop,
+ )
+
+ def on_terminal_resize(signum, stack) -> None:
+ self.write("\x1b[2J")
+ self.flush()
+ send_size_event()
+
+ signal.signal(signal.SIGWINCH, on_terminal_resize)
+
+ self.write("\x1b[?25l") # Hide cursor
+ self.write("\033[?1004h\n") # Enable FocusIn/FocusOut.
+
+ self._enable_mouse_support()
+ try:
+ self.attrs_before = termios.tcgetattr(self.fileno)
+ except termios.error:
+ # Ignore attribute errors.
+ self.attrs_before = None
+
+ try:
+ newattr = termios.tcgetattr(self.fileno)
+ except termios.error:
+ pass
+ else:
+ newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
+ newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])
+
+ # VMIN defines the number of characters read at a time in
+ # non-canonical mode. It seems to default to 1 on Linux, but on
+ # Solaris and derived operating systems it defaults to 4. (This is
+ # because the VMIN slot is the same as the VEOF slot, which
+ # defaults to ASCII EOT = Ctrl-D = 4.)
+ newattr[tty.CC][termios.VMIN] = 1
+
+ termios.tcsetattr(self.fileno, termios.TCSANOW, newattr)
+
+ self._key_thread = Thread(target=self._run_input_thread)
+ send_size_event()
+ self._key_thread.start()
+
+ @classmethod
+ def _patch_lflag(cls, attrs: int) -> int:
+ """Patch termios lflag.
+
+ Args:
+ attributes: New set attributes.
+
+ Returns:
+ New lflag.
+
+ """
+ # if TEXTUAL_ALLOW_SIGNALS env var is set, then allow Ctrl+C to send signals
+ ISIG = 0 if os.environ.get("TEXTUAL_ALLOW_SIGNALS") else termios.ISIG
+
+ return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | ISIG)
+
+ @classmethod
+ def _patch_iflag(cls, attrs: int) -> int:
+ return attrs & ~(
+ # Disable XON/XOFF flow control on output and input.
+ # (Don't capture Ctrl-S and Ctrl-Q.)
+ # Like executing: "stty -ixon."
+ termios.IXON
+ | termios.IXOFF
+ |
+ # Don't translate carriage return into newline on input.
+ termios.ICRNL
+ | termios.INLCR
+ | termios.IGNCR
+ )
+
+ def disable_input(self) -> None:
+ """Disable further input."""
+ try:
+ if not self.exit_event.is_set():
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ self._disable_mouse_support()
+ self.exit_event.set()
+ if self._key_thread is not None:
+ self._key_thread.join()
+ self.exit_event.clear()
+ termios.tcflush(self.fileno, termios.TCIFLUSH)
+
+ except Exception as error:
+ # TODO: log this
+ pass
+
+ def stop_application_mode(self) -> None:
+ """Stop application mode, restore state."""
+ self._disable_bracketed_paste()
+ self.disable_input()
+
+ self.write("\x1b[A\x1b[J")
+
+ if self.attrs_before is not None:
+ try:
+ termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
+ except termios.error:
+ pass
+
+ self.write("\x1b[?25h") # Show cursor
+ self.write("\033[?1004l\n") # Disable FocusIn/FocusOut.
+
+ self.flush()
diff --git a/src/textual/drivers/web_driver.py b/src/textual/drivers/web_driver.py
index 535e3109c0..e1fc7106a6 100644
--- a/src/textual/drivers/web_driver.py
+++ b/src/textual/drivers/web_driver.py
@@ -40,7 +40,12 @@ class WebDriver(Driver):
"""A headless driver that may be run remotely."""
def __init__(
- self, app: App, *, debug: bool = False, size: tuple[int, int] | None = None
+ self,
+ app: App,
+ *,
+ debug: bool = False,
+ mouse: bool = True,
+ size: tuple[int, int] | None = None,
):
if size is None:
try:
@@ -50,7 +55,7 @@ def __init__(
pass
else:
size = width, height
- super().__init__(app, debug=debug, size=size)
+ super().__init__(app, debug=debug, mouse=mouse, size=size)
self.stdout = sys.__stdout__
self.fileno = sys.__stdout__.fileno()
self._write = partial(os.write, self.fileno)
diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py
index 1df31728ac..9c53d4f6da 100644
--- a/src/textual/drivers/windows_driver.py
+++ b/src/textual/drivers/windows_driver.py
@@ -21,6 +21,7 @@ def __init__(
app: App,
*,
debug: bool = False,
+ mouse: bool = True,
size: tuple[int, int] | None = None,
) -> None:
"""Initialize Windows driver.
@@ -28,9 +29,10 @@ def __init__(
Args:
app: The App instance.
debug: Enable debug mode.
+ mouse: Enable mouse support.
size: Initial size of the terminal or `None` to detect.
"""
- super().__init__(app, debug=debug, size=size)
+ super().__init__(app, debug=debug, mouse=mouse, size=size)
self._file = sys.__stdout__
self.exit_event = Event()
self._event_thread: Thread | None = None
@@ -53,6 +55,8 @@ def write(self, data: str) -> None:
def _enable_mouse_support(self) -> None:
"""Enable reporting of mouse events."""
+ if not self._mouse:
+ return
write = self.write
write("\x1b[?1000h") # SET_VT200_MOUSE
write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE
@@ -62,6 +66,8 @@ def _enable_mouse_support(self) -> None:
def _disable_mouse_support(self) -> None:
"""Disable reporting of mouse events."""
+ if not self._mouse:
+ return
write = self.write
write("\x1b[?1000l")
write("\x1b[?1003l")
diff --git a/src/textual/events.py b/src/textual/events.py
index 68c3ae8f3d..ce5ecac4e3 100644
--- a/src/textual/events.py
+++ b/src/textual/events.py
@@ -55,6 +55,14 @@ class Shutdown(Event):
pass
+@dataclass
+class CursorPosition(Event, bubble=False):
+ """Internal event used to retrieve the terminal's cursor position."""
+
+ x: int
+ y: int
+
+
class Load(Event, bubble=False):
"""
Sent when the App is running but *before* the terminal is in application mode.
diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py
index 7c9ef51b0e..6e5a784c6a 100644
--- a/src/textual/message_pump.py
+++ b/src/textual/message_pump.py
@@ -350,7 +350,7 @@ def set_timer(
"""Make a function call after a delay.
Args:
- delay: Time to wait before invoking callback.
+ delay: Time (in seconds) to wait before invoking callback.
callback: Callback to call after time has expired.
name: Name of the timer (for debug).
pause: Start timer paused.
@@ -382,7 +382,7 @@ def set_interval(
"""Call a function at periodic intervals.
Args:
- interval: Time between calls.
+ interval: Time (in seconds) between calls.
callback: Function to call.
name: Name of the timer object.
repeat: Number of times to repeat the call or 0 for continuous.
diff --git a/src/textual/notifications.py b/src/textual/notifications.py
index 27f95ccd6d..2b2489bfbf 100644
--- a/src/textual/notifications.py
+++ b/src/textual/notifications.py
@@ -37,7 +37,7 @@ class Notification:
"""The severity level for the notification."""
timeout: float = 5
- """The timeout for the notification."""
+ """The timeout (in seconds) for the notification."""
raised_at: float = field(default_factory=time)
"""The time when the notification was raised (in Unix time)."""
diff --git a/src/textual/screen.py b/src/textual/screen.py
index b9713e7256..aa6410e31e 100644
--- a/src/textual/screen.py
+++ b/src/textual/screen.py
@@ -137,6 +137,13 @@ class Screen(Generic[ScreenResultType], Widget):
layout: vertical;
overflow-y: auto;
background: $surface;
+
+ &:inline {
+ height: auto;
+ min-height: 1;
+ border-top: tall $background;
+ border-bottom: tall $background;
+ }
}
"""
@@ -664,7 +671,20 @@ async def _on_idle(self, event: events.Idle) -> None:
def _compositor_refresh(self) -> None:
"""Perform a compositor refresh."""
- if self is self.app.screen:
+
+ if self.app.is_inline:
+ size = self.app.size
+ self.app._display(
+ self,
+ self._compositor.render_inline(
+ Size(size.width, self._get_inline_height(size)),
+ screen_stack=self.app._background_screens,
+ ),
+ )
+ self._dirty_widgets.clear()
+ self._compositor._dirty_regions.clear()
+
+ elif self is self.app.screen:
# Top screen
update = self._compositor.render_update(
screen_stack=self.app._background_screens
@@ -754,6 +774,8 @@ def _pop_result_callback(self) -> None:
def _refresh_layout(self, size: Size | None = None, scroll: bool = False) -> None:
"""Refresh the layout (can change size and positions of widgets)."""
size = self.outer_size if size is None else size
+ if self.app.is_inline:
+ size = Size(size.width, self._get_inline_height(self.app.size))
if not size:
return
self._compositor.update_widgets(self._dirty_widgets)
@@ -846,6 +868,30 @@ async def _on_update_scroll(self, message: messages.UpdateScroll) -> None:
self._scroll_required = True
self.check_idle()
+ def _get_inline_height(self, size: Size) -> int:
+ """Get the inline height (number of lines to display when running inline mode).
+
+ Args:
+ size: Size of the terminal
+
+ Returns:
+ Height for inline mode.
+ """
+ height_scalar = self.styles.height
+ if height_scalar is None or height_scalar.is_auto:
+ inline_height = self.get_content_height(size, size, size.width)
+ else:
+ inline_height = int(height_scalar.resolve(size, size))
+ inline_height += self.styles.gutter.height
+ min_height = self.styles.min_height
+ max_height = self.styles.max_height
+ if min_height is not None:
+ inline_height = max(inline_height, int(min_height.resolve(size, size)))
+ if max_height is not None:
+ inline_height = min(inline_height, int(max_height.resolve(size, size)))
+ inline_height = min(self.app.size.height - 1, inline_height)
+ return inline_height
+
def _screen_resized(self, size: Size):
"""Called by App when the screen is resized."""
self._refresh_layout(size)
diff --git a/src/textual/tree-sitter/highlights/bash.scm b/src/textual/tree-sitter/highlights/bash.scm
index 23bf03e697..f33a7c2d3a 100644
--- a/src/textual/tree-sitter/highlights/bash.scm
+++ b/src/textual/tree-sitter/highlights/bash.scm
@@ -1,145 +1,56 @@
-(simple_expansion) @none
-(expansion
- "${" @punctuation.special
- "}" @punctuation.special) @none
[
- "("
- ")"
- "(("
- "))"
- "{"
- "}"
- "["
- "]"
- "[["
- "]]"
- ] @punctuation.bracket
-
-[
- ";"
- ";;"
- (heredoc_start)
- ] @punctuation.delimiter
-
-[
- "$"
-] @punctuation.special
-
-[
- ">"
- ">>"
- "<"
- "<<"
- "&"
- "&&"
- "|"
- "||"
- "="
- "=~"
- "=="
- "!="
- ] @operator
-
-[
- (string)
- (raw_string)
- (ansi_c_string)
- (heredoc_body)
-] @string @spell
-
-(variable_assignment (word) @string)
-
-[
- "if"
- "then"
- "else"
- "elif"
- "fi"
- "case"
- "in"
- "esac"
- ] @conditional
-
-[
- "for"
- "do"
- "done"
- "select"
- "until"
- "while"
- ] @repeat
-
-[
- "declare"
- "export"
- "local"
- "readonly"
- "unset"
- ] @keyword
-
-"function" @keyword.function
-
-(special_variable_name) @constant
-
-; trap -l
-((word) @constant.builtin
- (#match? @constant.builtin "^SIG(HUP|INT|QUIT|ILL|TRAP|ABRT|BUS|FPE|KILL|USR[12]|SEGV|PIPE|ALRM|TERM|STKFLT|CHLD|CONT|STOP|TSTP|TT(IN|OU)|URG|XCPU|XFSZ|VTALRM|PROF|WINCH|IO|PWR|SYS|RTMIN([+]([1-9]|1[0-5]))?|RTMAX(-([1-9]|1[0-4]))?)$"))
-
-((word) @boolean
- (#any-of? @boolean "true" "false"))
-
-(comment) @comment @spell
-(test_operator) @string
-
-(command_substitution
- [ "$(" ")" ] @punctuation.bracket)
-
-(process_substitution
- [ "<(" ")" ] @punctuation.bracket)
-
-
-(function_definition
- name: (word) @function)
-
-(command_name (word) @function.call)
-
-((command_name (word) @function.builtin)
- (#any-of? @function.builtin
- "alias" "bg" "bind" "break" "builtin" "caller" "cd"
- "command" "compgen" "complete" "compopt" "continue"
- "coproc" "dirs" "disown" "echo" "enable" "eval"
- "exec" "exit" "fc" "fg" "getopts" "hash" "help"
- "history" "jobs" "kill" "let" "logout" "mapfile"
- "popd" "printf" "pushd" "pwd" "read" "readarray"
- "return" "set" "shift" "shopt" "source" "suspend"
- "test" "time" "times" "trap" "type" "typeset"
- "ulimit" "umask" "unalias" "wait"))
-
-(command
- argument: [
- (word) @parameter
- (concatenation (word) @parameter)
- ])
-
-((word) @number
- (#lua-match? @number "^[0-9]+$"))
-
-(file_redirect
- descriptor: (file_descriptor) @operator
- destination: (word) @parameter)
-
-(expansion
- [ "${" "}" ] @punctuation.bracket)
-
-(variable_name) @variable
-
-((variable_name) @constant
- (#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
-
-(case_item
- value: (word) @parameter)
-
-(regex) @string.regex
-
-((program . (comment) @preproc)
- (#lua-match? @preproc "^#!/"))
+ (string)
+ (raw_string)
+ (heredoc_body)
+ (heredoc_start)
+] @string
+
+(command_name) @function
+
+(variable_name) @property
+
+[
+ "case"
+ "do"
+ "done"
+ "elif"
+ "else"
+ "esac"
+ "export"
+ "fi"
+ "for"
+ "function"
+ "if"
+ "in"
+ "select"
+ "then"
+ "unset"
+ "until"
+ "while"
+] @keyword
+
+(comment) @comment
+
+(function_definition name: (word) @function)
+
+(file_descriptor) @number
+
+[
+ (command_substitution)
+ (process_substitution)
+ (expansion)
+]@embedded
+
+[
+ "$"
+ "&&"
+ ">"
+ ">>"
+ "<"
+ "|"
+] @operator
+
+(
+ (command (_) @constant)
+ (#match? @constant "^-")
+)
diff --git a/src/textual/tree-sitter/highlights/go.scm b/src/textual/tree-sitter/highlights/go.scm
new file mode 100644
index 0000000000..7e1d625272
--- /dev/null
+++ b/src/textual/tree-sitter/highlights/go.scm
@@ -0,0 +1,123 @@
+; Function calls
+
+(call_expression
+ function: (identifier) @function.builtin
+ (.match? @function.builtin "^(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$"))
+
+(call_expression
+ function: (identifier) @function)
+
+(call_expression
+ function: (selector_expression
+ field: (field_identifier) @function.method))
+
+; Function definitions
+
+(function_declaration
+ name: (identifier) @function)
+
+(method_declaration
+ name: (field_identifier) @function.method)
+
+; Identifiers
+
+(type_identifier) @type
+(field_identifier) @property
+(identifier) @variable
+
+; Operators
+
+[
+ "--"
+ "-"
+ "-="
+ ":="
+ "!"
+ "!="
+ "..."
+ "*"
+ "*"
+ "*="
+ "/"
+ "/="
+ "&"
+ "&&"
+ "&="
+ "%"
+ "%="
+ "^"
+ "^="
+ "+"
+ "++"
+ "+="
+ "<-"
+ "<"
+ "<<"
+ "<<="
+ "<="
+ "="
+ "=="
+ ">"
+ ">="
+ ">>"
+ ">>="
+ "|"
+ "|="
+ "||"
+ "~"
+] @operator
+
+; Keywords
+
+[
+ "break"
+ "case"
+ "chan"
+ "const"
+ "continue"
+ "default"
+ "defer"
+ "else"
+ "fallthrough"
+ "for"
+ "func"
+ "go"
+ "goto"
+ "if"
+ "import"
+ "interface"
+ "map"
+ "package"
+ "range"
+ "return"
+ "select"
+ "struct"
+ "switch"
+ "type"
+ "var"
+] @keyword
+
+; Literals
+
+[
+ (interpreted_string_literal)
+ (raw_string_literal)
+ (rune_literal)
+] @string
+
+(escape_sequence) @escape
+
+[
+ (int_literal)
+ (float_literal)
+ (imaginary_literal)
+] @number
+
+[
+ (true)
+ (false)
+ (nil)
+ (iota)
+] @constant.builtin
+
+(comment) @comment
diff --git a/src/textual/tree-sitter/highlights/java.scm b/src/textual/tree-sitter/highlights/java.scm
new file mode 100644
index 0000000000..301245b09a
--- /dev/null
+++ b/src/textual/tree-sitter/highlights/java.scm
@@ -0,0 +1,142 @@
+; Methods
+
+(method_declaration
+ name: (identifier) @function.method)
+(method_invocation
+ name: (identifier) @function.method)
+(super) @function.builtin
+
+; Annotations
+
+(annotation
+ name: (identifier) @attribute)
+(marker_annotation
+ name: (identifier) @attribute)
+
+"@" @operator
+
+; Types
+
+(type_identifier) @type
+
+(interface_declaration
+ name: (identifier) @type)
+(class_declaration
+ name: (identifier) @type)
+(enum_declaration
+ name: (identifier) @type)
+
+((field_access
+ object: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_identifier
+ scope: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((method_invocation
+ object: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((method_reference
+ . (identifier) @type)
+ (#match? @type "^[A-Z]"))
+
+(constructor_declaration
+ name: (identifier) @type)
+
+[
+ (boolean_type)
+ (integral_type)
+ (floating_point_type)
+ (floating_point_type)
+ (void_type)
+] @type.builtin
+
+; Variables
+
+((identifier) @constant
+ (#match? @constant "^_*[A-Z][A-Z\\d_]+$"))
+
+(identifier) @variable
+
+(this) @variable.builtin
+
+; Literals
+
+[
+ (hex_integer_literal)
+ (decimal_integer_literal)
+ (octal_integer_literal)
+ (decimal_floating_point_literal)
+ (hex_floating_point_literal)
+] @number
+
+[
+ (character_literal)
+ (string_literal)
+] @string
+(escape_sequence) @string.escape
+
+[
+ (true)
+ (false)
+ (null_literal)
+] @constant.builtin
+
+[
+ (line_comment)
+ (block_comment)
+] @comment
+
+; Keywords
+
+[
+ "abstract"
+ "assert"
+ "break"
+ "case"
+ "catch"
+ "class"
+ "continue"
+ "default"
+ "do"
+ "else"
+ "enum"
+ "exports"
+ "extends"
+ "final"
+ "finally"
+ "for"
+ "if"
+ "implements"
+ "import"
+ "instanceof"
+ "interface"
+ "module"
+ "native"
+ "new"
+ "non-sealed"
+ "open"
+ "opens"
+ "package"
+ "private"
+ "protected"
+ "provides"
+ "public"
+ "requires"
+ "record"
+ "return"
+ "sealed"
+ "static"
+ "strictfp"
+ "switch"
+ "synchronized"
+ "throw"
+ "throws"
+ "to"
+ "transient"
+ "transitive"
+ "try"
+ "uses"
+ "volatile"
+ "while"
+ "with"
+] @keyword
diff --git a/src/textual/tree-sitter/highlights/javascript.scm b/src/textual/tree-sitter/highlights/javascript.scm
new file mode 100644
index 0000000000..613a49a86f
--- /dev/null
+++ b/src/textual/tree-sitter/highlights/javascript.scm
@@ -0,0 +1,205 @@
+; Special identifiers
+;--------------------
+
+([
+ (identifier)
+ (shorthand_property_identifier)
+ (shorthand_property_identifier_pattern)
+ ] @constant
+ (#match? @constant "^[A-Z_][A-Z\\d_]+$"))
+
+
+((identifier) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+((identifier) @variable.builtin
+ (#match? @variable.builtin "^(arguments|module|console|window|document)$")
+ (#is-not? local))
+
+((identifier) @function.builtin
+ (#eq? @function.builtin "require")
+ (#is-not? local))
+
+; Function and method definitions
+;--------------------------------
+
+(function
+ name: (identifier) @function)
+(function_declaration
+ name: (identifier) @function)
+(method_definition
+ name: (property_identifier) @function.method)
+
+(pair
+ key: (property_identifier) @function.method
+ value: [(function) (arrow_function)])
+
+(assignment_expression
+ left: (member_expression
+ property: (property_identifier) @function.method)
+ right: [(function) (arrow_function)])
+
+(variable_declarator
+ name: (identifier) @function
+ value: [(function) (arrow_function)])
+
+(assignment_expression
+ left: (identifier) @function
+ right: [(function) (arrow_function)])
+
+; Function and method calls
+;--------------------------
+
+(call_expression
+ function: (identifier) @function)
+
+(call_expression
+ function: (member_expression
+ property: (property_identifier) @function.method))
+
+; Variables
+;----------
+
+(identifier) @variable
+
+; Properties
+;-----------
+
+(property_identifier) @property
+
+; Literals
+;---------
+
+(this) @variable.builtin
+(super) @variable.builtin
+
+[
+ (true)
+ (false)
+ (null)
+ (undefined)
+] @constant.builtin
+
+(comment) @comment
+
+[
+ (string)
+ (template_string)
+] @string
+
+(regex) @string.special
+(number) @number
+
+; Tokens
+;-------
+
+(template_substitution
+ "${" @punctuation.special
+ "}" @punctuation.special) @embedded
+
+[
+ ";"
+ (optional_chain)
+ "."
+ ","
+] @punctuation.delimiter
+
+[
+ "-"
+ "--"
+ "-="
+ "+"
+ "++"
+ "+="
+ "*"
+ "*="
+ "**"
+ "**="
+ "/"
+ "/="
+ "%"
+ "%="
+ "<"
+ "<="
+ "<<"
+ "<<="
+ "="
+ "=="
+ "==="
+ "!"
+ "!="
+ "!=="
+ "=>"
+ ">"
+ ">="
+ ">>"
+ ">>="
+ ">>>"
+ ">>>="
+ "~"
+ "^"
+ "&"
+ "|"
+ "^="
+ "&="
+ "|="
+ "&&"
+ "||"
+ "??"
+ "&&="
+ "||="
+ "??="
+] @operator
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+[
+ "as"
+ "async"
+ "await"
+ "break"
+ "case"
+ "catch"
+ "class"
+ "const"
+ "continue"
+ "debugger"
+ "default"
+ "delete"
+ "do"
+ "else"
+ "export"
+ "extends"
+ "finally"
+ "for"
+ "from"
+ "function"
+ "get"
+ "if"
+ "import"
+ "in"
+ "instanceof"
+ "let"
+ "new"
+ "of"
+ "return"
+ "set"
+ "static"
+ "switch"
+ "target"
+ "throw"
+ "try"
+ "typeof"
+ "var"
+ "void"
+ "while"
+ "with"
+ "yield"
+] @keyword
diff --git a/src/textual/tree-sitter/highlights/kotlin.scm b/src/textual/tree-sitter/highlights/kotlin.scm
new file mode 100644
index 0000000000..d2e15a68ee
--- /dev/null
+++ b/src/textual/tree-sitter/highlights/kotlin.scm
@@ -0,0 +1,380 @@
+;; Based on the nvim-treesitter highlighting, which is under the Apache license.
+;; See https://github.com/nvim-treesitter/nvim-treesitter/blob/f8ab59861eed4a1c168505e3433462ed800f2bae/queries/kotlin/highlights.scm
+;;
+;; The only difference in this file is that queries using #lua-match?
+;; have been removed.
+
+;;; Identifiers
+
+(simple_identifier) @variable
+
+; `it` keyword inside lambdas
+; FIXME: This will highlight the keyword outside of lambdas since tree-sitter
+; does not allow us to check for arbitrary nestation
+((simple_identifier) @variable.builtin
+(#eq? @variable.builtin "it"))
+
+; `field` keyword inside property getter/setter
+; FIXME: This will highlight the keyword outside of getters and setters
+; since tree-sitter does not allow us to check for arbitrary nestation
+((simple_identifier) @variable.builtin
+(#eq? @variable.builtin "field"))
+
+; `this` this keyword inside classes
+(this_expression) @variable.builtin
+
+; `super` keyword inside classes
+(super_expression) @variable.builtin
+
+(class_parameter
+ (simple_identifier) @property)
+
+(class_body
+ (property_declaration
+ (variable_declaration
+ (simple_identifier) @property)))
+
+; id_1.id_2.id_3: `id_2` and `id_3` are assumed as object properties
+(_
+ (navigation_suffix
+ (simple_identifier) @property))
+
+(enum_entry
+ (simple_identifier) @constant)
+
+(type_identifier) @type
+
+((type_identifier) @type.builtin
+ (#any-of? @type.builtin
+ "Byte"
+ "Short"
+ "Int"
+ "Long"
+ "UByte"
+ "UShort"
+ "UInt"
+ "ULong"
+ "Float"
+ "Double"
+ "Boolean"
+ "Char"
+ "String"
+ "Array"
+ "ByteArray"
+ "ShortArray"
+ "IntArray"
+ "LongArray"
+ "UByteArray"
+ "UShortArray"
+ "UIntArray"
+ "ULongArray"
+ "FloatArray"
+ "DoubleArray"
+ "BooleanArray"
+ "CharArray"
+ "Map"
+ "Set"
+ "List"
+ "EmptyMap"
+ "EmptySet"
+ "EmptyList"
+ "MutableMap"
+ "MutableSet"
+ "MutableList"
+))
+
+(package_header
+ . (identifier)) @namespace
+
+(import_header
+ "import" @include)
+
+
+; TODO: Seperate labeled returns/breaks/continue/super/this
+; Must be implemented in the parser first
+(label) @label
+
+;;; Function definitions
+
+(function_declaration
+ . (simple_identifier) @function)
+
+(getter
+ ("get") @function.builtin)
+(setter
+ ("set") @function.builtin)
+
+(primary_constructor) @constructor
+(secondary_constructor
+ ("constructor") @constructor)
+
+(constructor_invocation
+ (user_type
+ (type_identifier) @constructor))
+
+(anonymous_initializer
+ ("init") @constructor)
+
+(parameter
+ (simple_identifier) @parameter)
+
+(parameter_with_optional_type
+ (simple_identifier) @parameter)
+
+; lambda parameters
+(lambda_literal
+ (lambda_parameters
+ (variable_declaration
+ (simple_identifier) @parameter)))
+
+;;; Function calls
+
+; function()
+(call_expression
+ . (simple_identifier) @function)
+
+; object.function() or object.property.function()
+(call_expression
+ (navigation_expression
+ (navigation_suffix
+ (simple_identifier) @function) . ))
+
+(call_expression
+ . (simple_identifier) @function.builtin
+ (#any-of? @function.builtin
+ "arrayOf"
+ "arrayOfNulls"
+ "byteArrayOf"
+ "shortArrayOf"
+ "intArrayOf"
+ "longArrayOf"
+ "ubyteArrayOf"
+ "ushortArrayOf"
+ "uintArrayOf"
+ "ulongArrayOf"
+ "floatArrayOf"
+ "doubleArrayOf"
+ "booleanArrayOf"
+ "charArrayOf"
+ "emptyArray"
+ "mapOf"
+ "setOf"
+ "listOf"
+ "emptyMap"
+ "emptySet"
+ "emptyList"
+ "mutableMapOf"
+ "mutableSetOf"
+ "mutableListOf"
+ "print"
+ "println"
+ "error"
+ "TODO"
+ "run"
+ "runCatching"
+ "repeat"
+ "lazy"
+ "lazyOf"
+ "enumValues"
+ "enumValueOf"
+ "assert"
+ "check"
+ "checkNotNull"
+ "require"
+ "requireNotNull"
+ "with"
+ "suspend"
+ "synchronized"
+))
+
+;;; Literals
+
+[
+ (line_comment)
+ (multiline_comment)
+ (shebang_line)
+] @comment
+
+(real_literal) @float
+[
+ (integer_literal)
+ (long_literal)
+ (hex_literal)
+ (bin_literal)
+ (unsigned_literal)
+] @number
+
+[
+ "null" ; should be highlighted the same as booleans
+ (boolean_literal)
+] @boolean
+
+(character_literal) @character
+
+(string_literal) @string
+
+(character_escape_seq) @string.escape
+
+; There are 3 ways to define a regex
+; - "[abc]?".toRegex()
+(call_expression
+ (navigation_expression
+ ((string_literal) @string.regex)
+ (navigation_suffix
+ ((simple_identifier) @_function
+ (#eq? @_function "toRegex")))))
+
+; - Regex("[abc]?")
+(call_expression
+ ((simple_identifier) @_function
+ (#eq? @_function "Regex"))
+ (call_suffix
+ (value_arguments
+ (value_argument
+ (string_literal) @string.regex))))
+
+; - Regex.fromLiteral("[abc]?")
+(call_expression
+ (navigation_expression
+ ((simple_identifier) @_class
+ (#eq? @_class "Regex"))
+ (navigation_suffix
+ ((simple_identifier) @_function
+ (#eq? @_function "fromLiteral"))))
+ (call_suffix
+ (value_arguments
+ (value_argument
+ (string_literal) @string.regex))))
+
+;;; Keywords
+
+(type_alias "typealias" @keyword)
+[
+ (class_modifier)
+ (member_modifier)
+ (function_modifier)
+ (property_modifier)
+ (platform_modifier)
+ (variance_modifier)
+ (parameter_modifier)
+ (visibility_modifier)
+ (reification_modifier)
+ (inheritance_modifier)
+]@keyword
+
+[
+ "val"
+ "var"
+ "enum"
+ "class"
+ "object"
+ "interface"
+; "typeof" ; NOTE: It is reserved for future use
+] @keyword
+
+("fun") @keyword.function
+
+(jump_expression) @keyword.return
+
+[
+ "if"
+ "else"
+ "when"
+] @conditional
+
+[
+ "for"
+ "do"
+ "while"
+] @repeat
+
+[
+ "try"
+ "catch"
+ "throw"
+ "finally"
+] @exception
+
+
+(annotation
+ "@" @attribute (use_site_target)? @attribute)
+(annotation
+ (user_type
+ (type_identifier) @attribute))
+(annotation
+ (constructor_invocation
+ (user_type
+ (type_identifier) @attribute)))
+
+(file_annotation
+ "@" @attribute "file" @attribute ":" @attribute)
+(file_annotation
+ (user_type
+ (type_identifier) @attribute))
+(file_annotation
+ (constructor_invocation
+ (user_type
+ (type_identifier) @attribute)))
+
+;;; Operators & Punctuation
+
+[
+ "!"
+ "!="
+ "!=="
+ "="
+ "=="
+ "==="
+ ">"
+ ">="
+ "<"
+ "<="
+ "||"
+ "&&"
+ "+"
+ "++"
+ "+="
+ "-"
+ "--"
+ "-="
+ "*"
+ "*="
+ "/"
+ "/="
+ "%"
+ "%="
+ "?."
+ "?:"
+ "!!"
+ "is"
+ "!is"
+ "in"
+ "!in"
+ "as"
+ "as?"
+ ".."
+ "->"
+] @operator
+
+[
+ "(" ")"
+ "[" "]"
+ "{" "}"
+] @punctuation.bracket
+
+[
+ "."
+ ","
+ ";"
+ ":"
+ "::"
+] @punctuation.delimiter
+
+; NOTE: `interpolated_identifier`s can be highlighted in any way
+(string_literal
+ "$" @punctuation.special
+ (interpolated_identifier) @none)
+(string_literal
+ "${" @punctuation.special
+ (interpolated_expression) @none
+ "}" @punctuation.special)
diff --git a/src/textual/tree-sitter/highlights/python.scm b/src/textual/tree-sitter/highlights/python.scm
index 37aceef1fd..6f844b6135 100644
--- a/src/textual/tree-sitter/highlights/python.scm
+++ b/src/textual/tree-sitter/highlights/python.scm
@@ -15,20 +15,6 @@
((identifier) @constant
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
-((identifier) @constant.builtin
- (#lua-match? @constant.builtin "^__[a-zA-Z0-9_]*__$"))
-
-((identifier) @constant.builtin
- (#any-of? @constant.builtin
- ;; https://docs.python.org/3/library/constants.html
- "NotImplemented"
- "Ellipsis"
- "quit"
- "exit"
- "copyright"
- "credits"
- "license"))
-
((attribute
attribute: (identifier) @field)
(#match? @field "^([A-Z])@!.*$"))
diff --git a/src/textual/tree-sitter/highlights/regex.scm b/src/textual/tree-sitter/highlights/regex.scm
index 7c671c2c04..8b653465b4 100644
--- a/src/textual/tree-sitter/highlights/regex.scm
+++ b/src/textual/tree-sitter/highlights/regex.scm
@@ -1,34 +1,50 @@
-;; Forked from tree-sitter-regex
-;; The MIT License (MIT) Copyright (c) 2014 Max Brunsfeld
[
- "("
- ")"
- "(?"
- "(?:"
- "(?<"
- ">"
- "["
- "]"
- "{"
- "}"
-] @regex.punctuation.bracket
+ "("
+ ")"
+ "(?"
+ "(?:"
+ "(?<"
+ ">"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
(group_name) @property
-;; These are escaped special characters that lost their special meaning
-;; -> no special highlighting
-(identity_escape) @string.regex
-
-(class_character) @constant
+[
+ (identity_escape)
+ (control_letter_escape)
+ (character_class_escape)
+ (control_escape)
+ (start_assertion)
+ (end_assertion)
+ (boundary_assertion)
+ (non_boundary_assertion)
+] @escape
[
- (control_letter_escape)
- (character_class_escape)
- (control_escape)
- (start_assertion)
- (end_assertion)
- (boundary_assertion)
- (non_boundary_assertion)
-] @string.escape
+ "*"
+ "+"
+ "?"
+ "|"
+ "="
+ "!"
+] @operator
+
+(count_quantifier
+ [
+ (decimal_digits) @number
+ "," @punctuation.delimiter
+ ])
+
+(character_class
+ [
+ "^" @operator
+ (class_range "-" @operator)
+ ])
+
+(class_character) @constant.character
-[ "*" "+" "?" "|" "=" "!" ] @regex.operator
+(pattern_character) @string
diff --git a/src/textual/tree-sitter/highlights/rust.scm b/src/textual/tree-sitter/highlights/rust.scm
new file mode 100644
index 0000000000..c1556847b3
--- /dev/null
+++ b/src/textual/tree-sitter/highlights/rust.scm
@@ -0,0 +1,155 @@
+; Identifier conventions
+
+; Assume all-caps names are constants
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]+$'"))
+
+; Assume that uppercase names in paths are types
+((scoped_identifier
+ path: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_identifier
+ path: (scoped_identifier
+ name: (identifier) @type))
+ (#match? @type "^[A-Z]"))
+((scoped_type_identifier
+ path: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_type_identifier
+ path: (scoped_identifier
+ name: (identifier) @type))
+ (#match? @type "^[A-Z]"))
+
+; Assume other uppercase names are enum constructors
+((identifier) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+; Assume all qualified names in struct patterns are enum constructors. (They're
+; either that, or struct names; highlighting both as constructors seems to be
+; the less glaring choice of error, visually.)
+(struct_pattern
+ type: (scoped_type_identifier
+ name: (type_identifier) @constructor))
+
+; Function calls
+
+(call_expression
+ function: (identifier) @function)
+(call_expression
+ function: (field_expression
+ field: (field_identifier) @function.method))
+(call_expression
+ function: (scoped_identifier
+ "::"
+ name: (identifier) @function))
+
+(generic_function
+ function: (identifier) @function)
+(generic_function
+ function: (scoped_identifier
+ name: (identifier) @function))
+(generic_function
+ function: (field_expression
+ field: (field_identifier) @function.method))
+
+(macro_invocation
+ macro: (identifier) @function.macro
+ "!" @function.macro)
+
+; Function definitions
+
+(function_item (identifier) @function)
+(function_signature_item (identifier) @function)
+
+; Other identifiers
+
+(type_identifier) @type
+(primitive_type) @type.builtin
+(field_identifier) @property
+
+(line_comment) @comment
+(block_comment) @comment
+
+"(" @punctuation.bracket
+")" @punctuation.bracket
+"[" @punctuation.bracket
+"]" @punctuation.bracket
+"{" @punctuation.bracket
+"}" @punctuation.bracket
+
+(type_arguments
+ "<" @punctuation.bracket
+ ">" @punctuation.bracket)
+(type_parameters
+ "<" @punctuation.bracket
+ ">" @punctuation.bracket)
+
+"::" @punctuation.delimiter
+":" @punctuation.delimiter
+"." @punctuation.delimiter
+"," @punctuation.delimiter
+";" @punctuation.delimiter
+
+(parameter (identifier) @variable.parameter)
+
+(lifetime (identifier) @label)
+
+"as" @keyword
+"async" @keyword
+"await" @keyword
+"break" @keyword
+"const" @keyword
+"continue" @keyword
+"default" @keyword
+"dyn" @keyword
+"else" @keyword
+"enum" @keyword
+"extern" @keyword
+"fn" @keyword
+"for" @keyword
+"if" @keyword
+"impl" @keyword
+"in" @keyword
+"let" @keyword
+"loop" @keyword
+"macro_rules!" @keyword
+"match" @keyword
+"mod" @keyword
+"move" @keyword
+"pub" @keyword
+"ref" @keyword
+"return" @keyword
+"static" @keyword
+"struct" @keyword
+"trait" @keyword
+"type" @keyword
+"union" @keyword
+"unsafe" @keyword
+"use" @keyword
+"where" @keyword
+"while" @keyword
+(crate) @keyword
+(mutable_specifier) @keyword
+(use_list (self) @keyword)
+(scoped_use_list (self) @keyword)
+(scoped_identifier (self) @keyword)
+(super) @keyword
+
+(self) @variable.builtin
+
+(char_literal) @string
+(string_literal) @string
+(raw_string_literal) @string
+
+(boolean_literal) @constant.builtin
+(integer_literal) @constant.builtin
+(float_literal) @constant.builtin
+
+(escape_sequence) @escape
+
+(attribute_item) @attribute
+(inner_attribute_item) @attribute
+
+"*" @operator
+"&" @operator
+"'" @operator
diff --git a/src/textual/tree-sitter/highlights/toml.scm b/src/textual/tree-sitter/highlights/toml.scm
index 9228d28072..26e3982fef 100644
--- a/src/textual/tree-sitter/highlights/toml.scm
+++ b/src/textual/tree-sitter/highlights/toml.scm
@@ -9,7 +9,7 @@
;---------
(boolean) @boolean
-(comment) @comment @spell
+(comment) @comment
(string) @string
(integer) @number
(float) @float
diff --git a/src/textual/widget.py b/src/textual/widget.py
index 4bfaabdee0..e334c18ecf 100644
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -1792,7 +1792,7 @@ def animate(
attribute: Name of the attribute to animate.
value: The value to animate to.
final_value: The final value of the animation. Defaults to `value` if not set.
- duration: The duration of the animate.
+ duration: The duration (in seconds) of the animation.
speed: The speed of the animation.
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
@@ -3064,6 +3064,8 @@ def get_pseudo_classes(self) -> Iterable[str]:
yield "focus-within"
break
node = node._parent
+ if self.app.is_inline:
+ yield "inline"
def get_pseudo_class_state(self) -> PseudoClasses:
"""Get an object describing whether each pseudo class is present on this object or not.
@@ -3791,7 +3793,7 @@ def notify(
message: The message for the notification.
title: The title for the notification.
severity: The severity of the notification.
- timeout: The timeout for the notification.
+ timeout: The timeout (in seconds) for the notification.
See [`App.notify`][textual.app.App.notify] for the full
documentation for this method.
diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py
index 110646e109..532076c9f8 100644
--- a/src/textual/widgets/_button.py
+++ b/src/textual/widgets/_button.py
@@ -4,6 +4,7 @@
from typing import TYPE_CHECKING, cast
import rich.repr
+from rich.cells import cell_len
from rich.console import ConsoleRenderable, RenderableType
from rich.text import Text, TextType
from typing_extensions import Literal, Self
@@ -15,6 +16,7 @@
from ..binding import Binding
from ..css._error_tools import friendly_list
+from ..geometry import Size
from ..message import Message
from ..pad import HorizontalPad
from ..reactive import reactive
@@ -203,6 +205,13 @@ def __init__(
self.active_effect_duration = 0.3
"""Amount of time in seconds the button 'press' animation lasts."""
+ def get_content_width(self, container: Size, viewport: Size) -> int:
+ try:
+ return max([cell_len(line) for line in self.label.plain.splitlines()]) + 2
+ except ValueError:
+ # Empty string label
+ return 2
+
def __rich_repr__(self) -> rich.repr.Result:
yield from super().__rich_repr__()
yield "variant", self.variant, "default"
@@ -227,7 +236,7 @@ def validate_label(self, label: TextType) -> Text:
def render(self) -> RenderResult:
assert isinstance(self.label, Text)
label = self.label.copy()
- label.stylize(self.rich_style)
+ label.stylize_before(self.rich_style)
return HorizontalPad(
label,
1,
diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py
index 4ad2023aad..37231b21aa 100644
--- a/src/textual/widgets/_text_area.py
+++ b/src/textual/widgets/_text_area.py
@@ -1292,27 +1292,27 @@ def _undo_batch(self, edits: Sequence[Edit]) -> None:
return
old_gutter_width = self.gutter_width
- minimum_from = edits[-1].top
- maximum_old_end = (0, 0)
- maximum_new_end = (0, 0)
+ minimum_top = edits[-1].top
+ maximum_old_bottom = (0, 0)
+ maximum_new_bottom = (0, 0)
for edit in reversed(edits):
edit.undo(self)
end_location = (
edit._edit_result.end_location if edit._edit_result else (0, 0)
)
- if edit.from_location < minimum_from:
- minimum_from = edit.from_location
- if end_location > maximum_old_end:
- maximum_old_end = end_location
- if edit.to_location > maximum_new_end:
- maximum_new_end = edit.bottom
+ if edit.top < minimum_top:
+ minimum_top = edit.top
+ if end_location > maximum_old_bottom:
+ maximum_old_bottom = end_location
+ if edit.bottom > maximum_new_bottom:
+ maximum_new_bottom = edit.bottom
new_gutter_width = self.gutter_width
if old_gutter_width != new_gutter_width:
self.wrapped_document.wrap(self.wrap_width, self.indent_width)
else:
self.wrapped_document.wrap_range(
- minimum_from, maximum_old_end, maximum_new_end
+ minimum_top, maximum_old_bottom, maximum_new_bottom
)
self._refresh_size()
@@ -1338,29 +1338,29 @@ def _redo_batch(self, edits: Sequence[Edit]) -> None:
return
old_gutter_width = self.gutter_width
- minimum_from = edits[0].from_location
- maximum_old_end = (0, 0)
- maximum_new_end = (0, 0)
+ minimum_top = edits[0].top
+ maximum_old_bottom = (0, 0)
+ maximum_new_bottom = (0, 0)
for edit in edits:
edit.do(self, record_selection=False)
end_location = (
edit._edit_result.end_location if edit._edit_result else (0, 0)
)
- if edit.from_location < minimum_from:
- minimum_from = edit.from_location
- if end_location > maximum_new_end:
- maximum_new_end = end_location
- if edit.to_location > maximum_old_end:
- maximum_old_end = edit.to_location
+ if edit.top < minimum_top:
+ minimum_top = edit.top
+ if end_location > maximum_new_bottom:
+ maximum_new_bottom = end_location
+ if edit.bottom > maximum_old_bottom:
+ maximum_old_bottom = edit.bottom
new_gutter_width = self.gutter_width
if old_gutter_width != new_gutter_width:
self.wrapped_document.wrap(self.wrap_width, self.indent_width)
else:
self.wrapped_document.wrap_range(
- minimum_from,
- maximum_old_end,
- maximum_new_end,
+ minimum_top,
+ maximum_old_bottom,
+ maximum_new_bottom,
)
self._refresh_size()
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index c375135d8b..4051913429 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -2321,6 +2321,171 @@
'''
# ---
+# name: test_button_with_console_markup
+ '''
+
+
+ '''
+# ---
# name: test_buttons_render
'''