mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' of github.com:Textualize/textual into list-view
This commit is contained in:
6
.github/workflows/comment.yml
vendored
6
.github/workflows/comment.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
Did we solve your problem?
|
Don't forget to [star](https://github.com/Textualize/textual) the repository!
|
||||||
|
|
||||||
Glad we could help!
|
Follow [@textualizeio](https://twitter.com/textualizeio) for Textual updates.
|
||||||
|
|||||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,6 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
@@ -21,21 +22,30 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- It is now possible to `await` a `Widget.remove`.
|
- It is now possible to `await` a `Widget.remove`.
|
||||||
https://github.com/Textualize/textual/issues/1094
|
https://github.com/Textualize/textual/issues/1094
|
||||||
- It is now possible to `await` a `DOMQuery.remove`. Note that this changes
|
- It is now possible to `await` a `DOMQuery.remove`. Note that this changes
|
||||||
the return value of `DOMQuery.remove`, which uses to return `self`.
|
the return value of `DOMQuery.remove`, which used to return `self`.
|
||||||
https://github.com/Textualize/textual/issues/1094
|
https://github.com/Textualize/textual/issues/1094
|
||||||
- Added Pilot.wait_for_animation
|
- Added Pilot.wait_for_animation
|
||||||
- Added `Widget.move_child` https://github.com/Textualize/textual/issues/1121
|
- Added `Widget.move_child` https://github.com/Textualize/textual/issues/1121
|
||||||
|
- Added a `Label` widget https://github.com/Textualize/textual/issues/1190
|
||||||
|
- Support lazy-instantiated Screens (callables in App.SCREENS) https://github.com/Textualize/textual/pull/1185
|
||||||
|
- Display of keys in footer has more sensible defaults https://github.com/Textualize/textual/pull/1213
|
||||||
|
- Add App.get_key_display, allowing custom key_display App-wide https://github.com/Textualize/textual/pull/1213
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Watchers are now called immediately when setting the attribute if they are synchronous. https://github.com/Textualize/textual/pull/1145
|
- Watchers are now called immediately when setting the attribute if they are synchronous. https://github.com/Textualize/textual/pull/1145
|
||||||
- Widget.call_later has been renamed to Widget.call_after_refresh.
|
- Widget.call_later has been renamed to Widget.call_after_refresh.
|
||||||
|
- Button variant values are now checked at runtime. https://github.com/Textualize/textual/issues/1189
|
||||||
|
- Added caching of some properties in Styles object
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed DataTable row not updating after add https://github.com/Textualize/textual/issues/1026
|
- Fixed DataTable row not updating after add https://github.com/Textualize/textual/issues/1026
|
||||||
- Fixed issues with animation. Now objects of different types may be animated.
|
- Fixed issues with animation. Now objects of different types may be animated.
|
||||||
- Fixed containers with transparent background not showing borders https://github.com/Textualize/textual/issues/1175
|
- Fixed containers with transparent background not showing borders https://github.com/Textualize/textual/issues/1175
|
||||||
|
- Fixed auto-width in horizontal containers https://github.com/Textualize/textual/pull/1155
|
||||||
|
- Fixed Input cursor invisible when placeholder empty https://github.com/Textualize/textual/pull/1202
|
||||||
|
- Fixed deadlock when removing widgets from the App https://github.com/Textualize/textual/pull/1219
|
||||||
|
|
||||||
## [0.4.0] - 2022-11-08
|
## [0.4.0] - 2022-11-08
|
||||||
|
|
||||||
|
|||||||
10
docs.md
10
docs.md
@@ -1,14 +1,14 @@
|
|||||||
# Documentation Workflow
|
# Documentation Workflow
|
||||||
|
|
||||||
* [Install Hatch](https://hatch.pypa.io/latest/install/)
|
* Ensure you're inside a *Python 3.10+* virtual environment
|
||||||
* Run the live-reload server using `hatch run docs:serve` from the project root
|
* Run the live-reload server using `mkdocs serve` from the project root
|
||||||
* Create new pages by adding new directories and Markdown files inside `docs/*`
|
* Create new pages by adding new directories and Markdown files inside `docs/*`
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- `hatch run docs:serve` - Start the live-reloading docs server.
|
- `mkdocs serve` - Start the live-reloading docs server.
|
||||||
- `hatch run docs:build` - Build the documentation site.
|
- `mkdocs build` - Build the documentation site.
|
||||||
- `hatch run docs:help` - Print help message and exit.
|
- `mkdocs -h` - Print help message and exit.
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
|
|||||||
1
docs/api/label.md
Normal file
1
docs/api/label.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
::: textual.widgets.Label
|
||||||
@@ -2,3 +2,15 @@ willmcgugan:
|
|||||||
name: Will McGugan
|
name: Will McGugan
|
||||||
description: CEO / code-monkey
|
description: CEO / code-monkey
|
||||||
avatar: https://github.com/willmcgugan.png
|
avatar: https://github.com/willmcgugan.png
|
||||||
|
darrenburns:
|
||||||
|
name: Darren Burns
|
||||||
|
description: Code-monkey
|
||||||
|
avatar: https://github.com/darrenburns.png
|
||||||
|
davep:
|
||||||
|
name: Dave Pearson
|
||||||
|
description: Code-monkey
|
||||||
|
avatar: https://github.com/davep.png
|
||||||
|
rodrigo:
|
||||||
|
name: Rodrigo Girão Serrão
|
||||||
|
description: Code-monkey
|
||||||
|
avatar: https://github.com/rodrigogiraoserrao.png
|
||||||
|
|||||||
12
docs/examples/widgets/label.py
Normal file
12
docs/examples/widgets/label.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Label
|
||||||
|
|
||||||
|
|
||||||
|
class LabelApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Label("Hello, world!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = LabelApp()
|
||||||
|
app.run()
|
||||||
@@ -25,7 +25,7 @@ textual run my_app.py
|
|||||||
|
|
||||||
The `run` sub-command will first look for a `App` instance called `app` in the global scope of your Python file. If there is no `app`, it will create an instance of the first `App` class it finds and run that.
|
The `run` sub-command will first look for a `App` instance called `app` in the global scope of your Python file. If there is no `app`, it will create an instance of the first `App` class it finds and run that.
|
||||||
|
|
||||||
Alternatively, you can add the name of an `App` instance or class after a colon to run a specific app in the Python file. Here's an example:
|
Alternatively, you can add the name of an `App` instance or class after a colon to run a specific app in the Python file. Here's an example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
textual run my_app.py:alternative_app
|
textual run my_app.py:alternative_app
|
||||||
@@ -119,6 +119,6 @@ class LogApp(App):
|
|||||||
self.log(self.tree)
|
self.log(self.tree)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
LogApp.run()
|
LogApp().run()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
33
docs/widgets/label.md
Normal file
33
docs/widgets/label.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Label
|
||||||
|
|
||||||
|
A widget which displays static text, but which can also contain more complex Rich renderables.
|
||||||
|
|
||||||
|
- [ ] Focusable
|
||||||
|
- [ ] Container
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The example below shows how you can use a `Label` widget to display some text.
|
||||||
|
|
||||||
|
=== "Output"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/widgets/label.py"}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "label.py"
|
||||||
|
|
||||||
|
```python
|
||||||
|
--8<-- "docs/examples/widgets/label.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reactive Attributes
|
||||||
|
|
||||||
|
This widget has no reactive attributes.
|
||||||
|
|
||||||
|
## Messages
|
||||||
|
|
||||||
|
This widget sends no messages.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
* [Label](../api/label.md) code reference
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
# Static
|
# Static
|
||||||
|
|
||||||
A widget which displays static content.
|
A widget which displays static content.
|
||||||
Can be used for simple text labels, but can also contain more complex Rich renderables.
|
Can be used for Rich renderables and can also for the base for other types of widgets.
|
||||||
|
|
||||||
- [ ] Focusable
|
- [ ] Focusable
|
||||||
- [x] Container
|
- [ ] Container
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
The example below shows how you can use a `Static` widget as a simple text label.
|
The example below shows how you can use a `Static` widget as a simple text label (but see [Label](./label.md) as a way of displaying text).
|
||||||
|
|
||||||
=== "Output"
|
=== "Output"
|
||||||
|
|
||||||
@@ -32,3 +32,4 @@ This widget sends no messages.
|
|||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
* [Static](../api/static.md) code reference
|
* [Static](../api/static.md) code reference
|
||||||
|
* [Label](./label.md)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from textual.containers import Horizontal
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Footer, Button, Static
|
from textual.widgets import Footer, Button, Label
|
||||||
from textual.css.query import DOMQuery
|
from textual.css.query import DOMQuery
|
||||||
from textual.reactive import reactive
|
from textual.reactive import reactive
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding
|
||||||
@@ -33,10 +33,10 @@ class Help(Screen):
|
|||||||
Returns:
|
Returns:
|
||||||
ComposeResult: The result of composing the help screen.
|
ComposeResult: The result of composing the help screen.
|
||||||
"""
|
"""
|
||||||
yield Static(Markdown(Path(__file__).with_suffix(".md").read_text()))
|
yield Label(Markdown(Path(__file__).with_suffix(".md").read_text()))
|
||||||
|
|
||||||
|
|
||||||
class WinnerMessage(Static):
|
class WinnerMessage(Label):
|
||||||
"""Widget to tell the user they have won."""
|
"""Widget to tell the user they have won."""
|
||||||
|
|
||||||
MIN_MOVES: Final = 14
|
MIN_MOVES: Final = 14
|
||||||
@@ -91,9 +91,9 @@ class GameHeader(Widget):
|
|||||||
ComposeResult: The result of composing the game header.
|
ComposeResult: The result of composing the game header.
|
||||||
"""
|
"""
|
||||||
yield Horizontal(
|
yield Horizontal(
|
||||||
Static(self.app.title, id="app-title"),
|
Label(self.app.title, id="app-title"),
|
||||||
Static(id="moves"),
|
Label(id="moves"),
|
||||||
Static(id="progress"),
|
Label(id="progress"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def watch_moves(self, moves: int):
|
def watch_moves(self, moves: int):
|
||||||
@@ -102,7 +102,7 @@ class GameHeader(Widget):
|
|||||||
Args:
|
Args:
|
||||||
moves (int): The number of moves made.
|
moves (int): The number of moves made.
|
||||||
"""
|
"""
|
||||||
self.query_one("#moves", Static).update(f"Moves: {moves}")
|
self.query_one("#moves", Label).update(f"Moves: {moves}")
|
||||||
|
|
||||||
def watch_filled(self, filled: int):
|
def watch_filled(self, filled: int):
|
||||||
"""Watch the on-count reactive and update when it changes.
|
"""Watch the on-count reactive and update when it changes.
|
||||||
@@ -110,7 +110,7 @@ class GameHeader(Widget):
|
|||||||
Args:
|
Args:
|
||||||
filled (int): The number of cells that are currently on.
|
filled (int): The number of cells that are currently on.
|
||||||
"""
|
"""
|
||||||
self.query_one("#progress", Static).update(f"Filled: {filled}")
|
self.query_one("#progress", Label).update(f"Filled: {filled}")
|
||||||
|
|
||||||
|
|
||||||
class GameCell(Button):
|
class GameCell(Button):
|
||||||
@@ -311,7 +311,7 @@ class FiveByFive(App[None]):
|
|||||||
CSS_PATH = "five_by_five.css"
|
CSS_PATH = "five_by_five.css"
|
||||||
"""The name of the stylesheet for the app."""
|
"""The name of the stylesheet for the app."""
|
||||||
|
|
||||||
SCREENS = {"help": Help()}
|
SCREENS = {"help": Help}
|
||||||
"""The pre-loaded screens for the application."""
|
"""The pre-loaded screens for the application."""
|
||||||
|
|
||||||
BINDINGS = [("ctrl+d", "toggle_dark", "Toggle Dark Mode")]
|
BINDINGS = [("ctrl+d", "toggle_dark", "Toggle Dark Mode")]
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ nav:
|
|||||||
- "widgets/footer.md"
|
- "widgets/footer.md"
|
||||||
- "widgets/header.md"
|
- "widgets/header.md"
|
||||||
- "widgets/input.md"
|
- "widgets/input.md"
|
||||||
|
- "widgets/label.md"
|
||||||
- "widgets/static.md"
|
- "widgets/static.md"
|
||||||
- "widgets/tree_control.md"
|
- "widgets/tree_control.md"
|
||||||
- API:
|
- API:
|
||||||
@@ -109,6 +110,7 @@ nav:
|
|||||||
- "api/footer.md"
|
- "api/footer.md"
|
||||||
- "api/geometry.md"
|
- "api/geometry.md"
|
||||||
- "api/header.md"
|
- "api/header.md"
|
||||||
|
- "api/label.md"
|
||||||
- "api/list_view.md"
|
- "api/list_view.md"
|
||||||
- "api/message_pump.md"
|
- "api/message_pump.md"
|
||||||
- "api/message.md"
|
- "api/message.md"
|
||||||
@@ -216,10 +218,10 @@ extra_css:
|
|||||||
|
|
||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
- icon: fontawesome/brands/twitter
|
- icon: fontawesome/brands/twitter
|
||||||
link: https://twitter.com/textualizeio
|
link: https://twitter.com/textualizeio
|
||||||
name: textualizeio on Twitter
|
name: textualizeio on Twitter
|
||||||
- icon: fontawesome/brands/github
|
- icon: fontawesome/brands/github
|
||||||
link: https://github.com/textualize/textual/
|
link: https://github.com/textualize/textual/
|
||||||
name: Textual on Github
|
name: Textual on Github
|
||||||
- icon: fontawesome/brands/discord
|
- icon: fontawesome/brands/discord
|
||||||
|
|||||||
170
poetry.lock
generated
170
poetry.lock
generated
@@ -31,6 +31,24 @@ python-versions = ">=3.7"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
frozenlist = ">=1.1.0"
|
frozenlist = ">=1.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "3.6.2"
|
||||||
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.8"
|
||||||
|
sniffio = ">=1.1"
|
||||||
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||||
|
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||||
|
trio = ["trio (>=0.16,<0.22)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-timeout"
|
name = "async-timeout"
|
||||||
version = "4.0.2"
|
version = "4.0.2"
|
||||||
@@ -144,7 +162,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "1.4.3"
|
version = "1.4.4"
|
||||||
description = "Simple library for color and formatting to terminal"
|
description = "Simple library for color and formatting to terminal"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -182,7 +200,7 @@ python-versions = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exceptiongroup"
|
name = "exceptiongroup"
|
||||||
version = "1.0.1"
|
version = "1.0.4"
|
||||||
description = "Backport of PEP 654 (exception groups)"
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -250,7 +268,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffe"
|
name = "griffe"
|
||||||
version = "0.23.0"
|
version = "0.24.0"
|
||||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -258,10 +276,60 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
cached-property = {version = "*", markers = "python_version < \"3.8\""}
|
cached-property = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
colorama = ">=0.4"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
async = ["aiofiles (>=0.7,<1.0)"]
|
async = ["aiofiles (>=0.7,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.14.0"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "0.16.1"
|
||||||
|
description = "A minimal low-level HTTP client."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = ">=3.0,<5.0"
|
||||||
|
certifi = "*"
|
||||||
|
h11 = ">=0.13,<0.15"
|
||||||
|
sniffio = ">=1.0.0,<2.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.23.1"
|
||||||
|
description = "The next generation HTTP client."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = "*"
|
||||||
|
httpcore = ">=0.15.0,<0.17.0"
|
||||||
|
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||||
|
sniffio = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli", "brotlicffi"]
|
||||||
|
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -390,7 +458,7 @@ mkdocs = ">=1.1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "8.5.9"
|
version = "8.5.10"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -455,14 +523,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings-python"
|
name = "mkdocstrings-python"
|
||||||
version = "0.7.1"
|
version = "0.8.0"
|
||||||
description = "A Python handler for mkdocstrings."
|
description = "A Python handler for mkdocstrings."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
griffe = ">=0.11.1"
|
griffe = ">=0.24"
|
||||||
mkdocstrings = ">=0.19"
|
mkdocstrings = ">=0.19"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -541,7 +609,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -549,7 +617,7 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "2.5.3"
|
version = "2.5.4"
|
||||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -663,7 +731,7 @@ testing = ["coverage (==6.2)", "mypy (==0.931)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-asyncio"
|
name = "pytest-asyncio"
|
||||||
version = "0.20.1"
|
version = "0.20.2"
|
||||||
description = "Pytest support for asyncio"
|
description = "Pytest support for asyncio"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -748,6 +816,20 @@ urllib3 = ">=1.21.1,<1.27"
|
|||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfc3986"
|
||||||
|
version = "1.5.0"
|
||||||
|
description = "Validating URI References per RFC 3986"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
idna2008 = ["idna"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "12.6.0"
|
version = "12.6.0"
|
||||||
@@ -793,6 +875,14 @@ category = "dev"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.0"
|
||||||
|
description = "Sniff out which async library your code is running under"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syrupy"
|
name = "syrupy"
|
||||||
version = "3.0.5"
|
version = "3.0.5"
|
||||||
@@ -871,7 +961,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "virtualenv"
|
name = "virtualenv"
|
||||||
version = "20.16.6"
|
version = "20.16.7"
|
||||||
description = "Virtual Python Environment builder"
|
description = "Virtual Python Environment builder"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -929,7 +1019,7 @@ dev = ["aiohttp", "click", "msgpack"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "0c33f462f79d31068c0d74743f47c1d713ecc8ba009eaef1bef832f1272e4b1a"
|
content-hash = "578d7f611a797d406b8db7c61f28796e81af2e637d9671caab9b4ea2b1cf93c6"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiohttp = [
|
aiohttp = [
|
||||||
@@ -1025,6 +1115,10 @@ aiosignal = [
|
|||||||
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
|
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
|
||||||
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
|
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
|
||||||
]
|
]
|
||||||
|
anyio = [
|
||||||
|
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
|
||||||
|
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
|
||||||
|
]
|
||||||
async-timeout = [
|
async-timeout = [
|
||||||
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
||||||
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||||
@@ -1085,7 +1179,7 @@ colorama = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
colored = [
|
colored = [
|
||||||
{file = "colored-1.4.3.tar.gz", hash = "sha256:b7b48b9f40e8a65bbb54813d5d79dd008dc8b8c5638d5bbfd30fc5a82e6def7a"},
|
{file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"},
|
||||||
]
|
]
|
||||||
commonmark = [
|
commonmark = [
|
||||||
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
||||||
@@ -1148,8 +1242,8 @@ distlib = [
|
|||||||
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
|
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
|
||||||
]
|
]
|
||||||
exceptiongroup = [
|
exceptiongroup = [
|
||||||
{file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"},
|
{file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
|
||||||
{file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"},
|
{file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
|
||||||
]
|
]
|
||||||
filelock = [
|
filelock = [
|
||||||
{file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
|
{file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
|
||||||
@@ -1244,8 +1338,20 @@ gitpython = [
|
|||||||
{file = "GitPython-3.1.29.tar.gz", hash = "sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd"},
|
{file = "GitPython-3.1.29.tar.gz", hash = "sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd"},
|
||||||
]
|
]
|
||||||
griffe = [
|
griffe = [
|
||||||
{file = "griffe-0.23.0-py3-none-any.whl", hash = "sha256:cfca5f523808109da3f8cfaa46e325fa2e5bef51120d1146e908c121b56475f0"},
|
{file = "griffe-0.24.0-py3-none-any.whl", hash = "sha256:6c6b64716155f27ef63377e2b04749079c359f06d9a6e638bb2f885cbe463360"},
|
||||||
{file = "griffe-0.23.0.tar.gz", hash = "sha256:a639e2968c8e27f56ebcc57f869a03cea7ac7e7f5684bd2429c665f761c4e7bd"},
|
{file = "griffe-0.24.0.tar.gz", hash = "sha256:afa92aeb8c5a4f2501693ffd607f820d7ade3ac2a36e34c43d39ee3486cec392"},
|
||||||
|
]
|
||||||
|
h11 = [
|
||||||
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
|
]
|
||||||
|
httpcore = [
|
||||||
|
{file = "httpcore-0.16.1-py3-none-any.whl", hash = "sha256:8d393db683cc8e35cc6ecb02577c5e1abfedde52b38316d038932a84b4875ecb"},
|
||||||
|
{file = "httpcore-0.16.1.tar.gz", hash = "sha256:3d3143ff5e1656a5740ea2f0c167e8e9d48c5a9bbd7f00ad1f8cff5711b08543"},
|
||||||
|
]
|
||||||
|
httpx = [
|
||||||
|
{file = "httpx-0.23.1-py3-none-any.whl", hash = "sha256:0b9b1f0ee18b9978d637b0776bfd7f54e2ca278e063e3586d8f01cda89e042a8"},
|
||||||
|
{file = "httpx-0.23.1.tar.gz", hash = "sha256:202ae15319be24efe9a8bd4ed4360e68fde7b38bcc2ce87088d416f026667d19"},
|
||||||
]
|
]
|
||||||
identify = [
|
identify = [
|
||||||
{file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"},
|
{file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"},
|
||||||
@@ -1326,8 +1432,8 @@ mkdocs-autorefs = [
|
|||||||
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
||||||
]
|
]
|
||||||
mkdocs-material = [
|
mkdocs-material = [
|
||||||
{file = "mkdocs_material-8.5.9-py3-none-any.whl", hash = "sha256:143ea55843b3747b640e1110824d91e8a4c670352380e166e64959f9abe98862"},
|
{file = "mkdocs_material-8.5.10-py3-none-any.whl", hash = "sha256:51760fa4c9ee3ca0b3a661ec9f9817ec312961bb84ff19e5b523fdc5256e1d6c"},
|
||||||
{file = "mkdocs_material-8.5.9.tar.gz", hash = "sha256:45eeabb23d2caba8fa3b85c91d9ec8e8b22add716e9bba8faf16d56af8aa5622"},
|
{file = "mkdocs_material-8.5.10.tar.gz", hash = "sha256:7623608f746c6d9ff68a8ef01f13eddf32fa2cae5e15badb251f26d1196bc8f1"},
|
||||||
]
|
]
|
||||||
mkdocs-material-extensions = [
|
mkdocs-material-extensions = [
|
||||||
{file = "mkdocs_material_extensions-1.1-py3-none-any.whl", hash = "sha256:bcc2e5fc70c0ec50e59703ee6e639d87c7e664c0c441c014ea84461a90f1e902"},
|
{file = "mkdocs_material_extensions-1.1-py3-none-any.whl", hash = "sha256:bcc2e5fc70c0ec50e59703ee6e639d87c7e664c0c441c014ea84461a90f1e902"},
|
||||||
@@ -1342,8 +1448,8 @@ mkdocstrings = [
|
|||||||
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
|
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
|
||||||
]
|
]
|
||||||
mkdocstrings-python = [
|
mkdocstrings-python = [
|
||||||
{file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
|
{file = "mkdocstrings-python-0.8.0.tar.gz", hash = "sha256:67f674a8b252fca0b9411c10fb923dd6aacc49ac55c59f738b78b06592ace43d"},
|
||||||
{file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
|
{file = "mkdocstrings_python-0.8.0-py3-none-any.whl", hash = "sha256:cbee42e53aeaae340d79d72e9bcf42f2b6abe4d11696597c76e3e86a4d9f05a0"},
|
||||||
]
|
]
|
||||||
msgpack = [
|
msgpack = [
|
||||||
{file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
|
{file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
|
||||||
@@ -1509,12 +1615,12 @@ packaging = [
|
|||||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||||
]
|
]
|
||||||
pathspec = [
|
pathspec = [
|
||||||
{file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
|
{file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"},
|
||||||
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
|
{file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"},
|
||||||
]
|
]
|
||||||
platformdirs = [
|
platformdirs = [
|
||||||
{file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"},
|
{file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
|
||||||
{file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"},
|
{file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
|
||||||
]
|
]
|
||||||
pluggy = [
|
pluggy = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
@@ -1545,8 +1651,8 @@ pytest-aiohttp = [
|
|||||||
{file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"},
|
{file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"},
|
||||||
]
|
]
|
||||||
pytest-asyncio = [
|
pytest-asyncio = [
|
||||||
{file = "pytest-asyncio-0.20.1.tar.gz", hash = "sha256:626699de2a747611f3eeb64168b3575f70439b06c3d0206e6ceaeeb956e65519"},
|
{file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"},
|
||||||
{file = "pytest_asyncio-0.20.1-py3-none-any.whl", hash = "sha256:2c85a835df33fda40fe3973b451e0c194ca11bc2c007eabff90bb3d156fc172b"},
|
{file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"},
|
||||||
]
|
]
|
||||||
pytest-cov = [
|
pytest-cov = [
|
||||||
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
|
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
|
||||||
@@ -1610,6 +1716,10 @@ requests = [
|
|||||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
]
|
]
|
||||||
|
rfc3986 = [
|
||||||
|
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||||
|
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||||
|
]
|
||||||
rich = [
|
rich = [
|
||||||
{file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
|
{file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
|
||||||
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
|
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
|
||||||
@@ -1626,6 +1736,10 @@ smmap = [
|
|||||||
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
|
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
|
||||||
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
|
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
|
||||||
]
|
]
|
||||||
|
sniffio = [
|
||||||
|
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||||
|
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||||
|
]
|
||||||
syrupy = [
|
syrupy = [
|
||||||
{file = "syrupy-3.0.5-py3-none-any.whl", hash = "sha256:6dc0472cc690782b517d509a2854b5ca552a9851acf43b8a847cfa1aed6f6b01"},
|
{file = "syrupy-3.0.5-py3-none-any.whl", hash = "sha256:6dc0472cc690782b517d509a2854b5ca552a9851acf43b8a847cfa1aed6f6b01"},
|
||||||
{file = "syrupy-3.0.5.tar.gz", hash = "sha256:928962a6c14abb2695be9a49b42a016a4e4abdb017a69104cde958f2faf01f98"},
|
{file = "syrupy-3.0.5.tar.gz", hash = "sha256:928962a6c14abb2695be9a49b42a016a4e4abdb017a69104cde958f2faf01f98"},
|
||||||
@@ -1729,8 +1843,8 @@ urllib3 = [
|
|||||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
||||||
]
|
]
|
||||||
virtualenv = [
|
virtualenv = [
|
||||||
{file = "virtualenv-20.16.6-py3-none-any.whl", hash = "sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108"},
|
{file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"},
|
||||||
{file = "virtualenv-20.16.6.tar.gz", hash = "sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e"},
|
{file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"},
|
||||||
]
|
]
|
||||||
watchdog = [
|
watchdog = [
|
||||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
|
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ nanoid = ">=2.0.0"
|
|||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
dev = ["aiohttp", "click", "msgpack"]
|
dev = ["aiohttp", "click", "msgpack"]
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = "^7.1.3"
|
pytest = "^7.1.3"
|
||||||
black = "^22.3.0"
|
black = "^22.3.0"
|
||||||
mypy = "^0.990"
|
mypy = "^0.990"
|
||||||
@@ -57,9 +57,8 @@ pytest-aiohttp = "^1.0.4"
|
|||||||
time-machine = "^2.6.0"
|
time-machine = "^2.6.0"
|
||||||
Jinja2 = "<3.1.0"
|
Jinja2 = "<3.1.0"
|
||||||
syrupy = "^3.0.0"
|
syrupy = "^3.0.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
mkdocs-rss-plugin = "^1.5.0"
|
mkdocs-rss-plugin = "^1.5.0"
|
||||||
|
httpx = "^0.23.1"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
includes = "src"
|
includes = "src"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from time import perf_counter
|
|||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
|
Callable,
|
||||||
Generic,
|
Generic,
|
||||||
Iterable,
|
Iterable,
|
||||||
List,
|
List,
|
||||||
@@ -44,7 +45,8 @@ from ._context import active_app
|
|||||||
from ._event_broker import NoHandler, extract_handler_actions
|
from ._event_broker import NoHandler, extract_handler_actions
|
||||||
from ._filter import LineFilter, Monochrome
|
from ._filter import LineFilter, Monochrome
|
||||||
from ._path import _make_path_object_relative
|
from ._path import _make_path_object_relative
|
||||||
from ._typing import TypeAlias, Final
|
from ._typing import Final, TypeAlias
|
||||||
|
from .await_remove import AwaitRemove
|
||||||
from .binding import Binding, Bindings
|
from .binding import Binding, Bindings
|
||||||
from .css.query import NoMatches
|
from .css.query import NoMatches
|
||||||
from .css.stylesheet import Stylesheet
|
from .css.stylesheet import Stylesheet
|
||||||
@@ -55,12 +57,13 @@ from .drivers.headless_driver import HeadlessDriver
|
|||||||
from .features import FeatureFlag, parse_features
|
from .features import FeatureFlag, parse_features
|
||||||
from .file_monitor import FileMonitor
|
from .file_monitor import FileMonitor
|
||||||
from .geometry import Offset, Region, Size
|
from .geometry import Offset, Region, Size
|
||||||
from .keys import REPLACED_KEYS
|
from .keys import REPLACED_KEYS, _get_key_display
|
||||||
from .messages import CallbackType
|
from .messages import CallbackType
|
||||||
from .reactive import Reactive
|
from .reactive import Reactive
|
||||||
from .renderables.blank import Blank
|
from .renderables.blank import Blank
|
||||||
from .screen import Screen
|
from .screen import Screen
|
||||||
from .widget import AwaitMount, Widget, MountError
|
from .widget import AwaitMount, MountError, Widget
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .devtools.client import DevtoolsClient
|
from .devtools.client import DevtoolsClient
|
||||||
@@ -101,7 +104,6 @@ DEFAULT_COLORS = {
|
|||||||
ComposeResult = Iterable[Widget]
|
ComposeResult = Iterable[Widget]
|
||||||
RenderResult = RenderableType
|
RenderResult = RenderableType
|
||||||
|
|
||||||
|
|
||||||
AutopilotCallbackType: TypeAlias = "Callable[[Pilot], Coroutine[Any, Any, None]]"
|
AutopilotCallbackType: TypeAlias = "Callable[[Pilot], Coroutine[Any, Any, None]]"
|
||||||
|
|
||||||
|
|
||||||
@@ -228,7 +230,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SCREENS: dict[str, Screen] = {}
|
SCREENS: dict[str, Screen | Callable[[], Screen]] = {}
|
||||||
_BASE_PATH: str | None = None
|
_BASE_PATH: str | None = None
|
||||||
CSS_PATH: CSSPathType = None
|
CSS_PATH: CSSPathType = None
|
||||||
TITLE: str | None = None
|
TITLE: str | None = None
|
||||||
@@ -332,7 +334,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._registry: WeakSet[DOMNode] = WeakSet()
|
self._registry: WeakSet[DOMNode] = WeakSet()
|
||||||
|
|
||||||
self._installed_screens: WeakValueDictionary[
|
self._installed_screens: WeakValueDictionary[
|
||||||
str, Screen
|
str, Screen | Callable[[], Screen]
|
||||||
] = WeakValueDictionary()
|
] = WeakValueDictionary()
|
||||||
self._installed_screens.update(**self.SCREENS)
|
self._installed_screens.update(**self.SCREENS)
|
||||||
|
|
||||||
@@ -354,6 +356,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
self._screenshot: str | None = None
|
self._screenshot: str | None = None
|
||||||
|
self._dom_lock = asyncio.Lock()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def return_value(self) -> ReturnType | None:
|
def return_value(self) -> ReturnType | None:
|
||||||
@@ -669,6 +672,22 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
keys, action, description, show=show, key_display=key_display
|
keys, action, description, show=show, key_display=key_display
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_key_display(self, key: str) -> str:
|
||||||
|
"""For a given key, return how it should be displayed in an app
|
||||||
|
(e.g. in the Footer widget).
|
||||||
|
By key, we refer to the string used in the "key" argument for
|
||||||
|
a Binding instance. By overriding this method, you can ensure that
|
||||||
|
keys are displayed consistently throughout your app, without
|
||||||
|
needing to add a key_display to every binding.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): The binding key string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The display string for the input key.
|
||||||
|
"""
|
||||||
|
return _get_key_display(key)
|
||||||
|
|
||||||
async def _press_keys(self, keys: Iterable[str]) -> None:
|
async def _press_keys(self, keys: Iterable[str]) -> None:
|
||||||
"""A task to send key events."""
|
"""A task to send key events."""
|
||||||
app = self
|
app = self
|
||||||
@@ -706,7 +725,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
# This conditional sleep can be removed after that issue is closed.
|
# This conditional sleep can be removed after that issue is closed.
|
||||||
if key == "tab":
|
if key == "tab":
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.05)
|
||||||
await asyncio.sleep(0.02)
|
await asyncio.sleep(0.025)
|
||||||
await app._animator.wait_for_idle()
|
await app._animator.wait_for_idle()
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -1000,12 +1019,15 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
next_screen = self._installed_screens[screen]
|
next_screen = self._installed_screens[screen]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(f"No screen called {screen!r} installed") from None
|
raise KeyError(f"No screen called {screen!r} installed") from None
|
||||||
|
if callable(next_screen):
|
||||||
|
next_screen = next_screen()
|
||||||
|
self._installed_screens[screen] = next_screen
|
||||||
else:
|
else:
|
||||||
next_screen = screen
|
next_screen = screen
|
||||||
return next_screen
|
return next_screen
|
||||||
|
|
||||||
def _get_screen(self, screen: Screen | str) -> tuple[Screen, AwaitMount]:
|
def _get_screen(self, screen: Screen | str) -> tuple[Screen, AwaitMount]:
|
||||||
"""Get an installed screen and a await mount object.
|
"""Get an installed screen and an AwaitMount object.
|
||||||
|
|
||||||
If the screen isn't running, it will be registered before it is run.
|
If the screen isn't running, it will be registered before it is run.
|
||||||
|
|
||||||
@@ -1560,7 +1582,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
|
|
||||||
# Close pre-defined screens
|
# Close pre-defined screens
|
||||||
for screen in self.SCREENS.values():
|
for screen in self.SCREENS.values():
|
||||||
if screen._running:
|
if isinstance(screen, Screen) and screen._running:
|
||||||
await self._prune_node(screen)
|
await self._prune_node(screen)
|
||||||
|
|
||||||
# Close any remaining nodes
|
# Close any remaining nodes
|
||||||
@@ -1938,6 +1960,48 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
for child in widget.children:
|
for child in widget.children:
|
||||||
push(child)
|
push(child)
|
||||||
|
|
||||||
|
def _remove_nodes(self, widgets: list[Widget]) -> AwaitRemove:
|
||||||
|
"""Remove nodes from DOM, and return an awaitable that awaits cleanup.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widgets (list[Widget]): List of nodes to remvoe.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AwaitRemove: Awaitable that returns when the nodes have been fully removed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def prune_widgets_task(
|
||||||
|
widgets: list[Widget], finished_event: asyncio.Event
|
||||||
|
) -> None:
|
||||||
|
"""Prune widgets as a background task.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widgets (list[Widget]): Widgets to prune.
|
||||||
|
finished_event (asyncio.Event): Event to set when complete.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self._prune_nodes(widgets)
|
||||||
|
finally:
|
||||||
|
finished_event.set()
|
||||||
|
|
||||||
|
removed_widgets = self._detach_from_dom(widgets)
|
||||||
|
self.refresh(layout=True)
|
||||||
|
|
||||||
|
finished_event = asyncio.Event()
|
||||||
|
asyncio.create_task(prune_widgets_task(removed_widgets, finished_event))
|
||||||
|
|
||||||
|
return AwaitRemove(finished_event)
|
||||||
|
|
||||||
|
async def _prune_nodes(self, widgets: list[Widget]) -> None:
|
||||||
|
"""Remove nodes and children.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widgets (Widget): _description_
|
||||||
|
"""
|
||||||
|
async with self._dom_lock:
|
||||||
|
for widget in widgets:
|
||||||
|
await self._prune_node(widget)
|
||||||
|
|
||||||
async def _prune_node(self, root: Widget) -> None:
|
async def _prune_node(self, root: Widget) -> None:
|
||||||
"""Remove a node and its children. Children are removed before parents.
|
"""Remove a node and its children. Children are removed before parents.
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,3 @@ def camel_to_snake(
|
|||||||
return f"{lower}_{upper.lower()}"
|
return f"{lower}_{upper.lower()}"
|
||||||
|
|
||||||
return _re_snake.sub(repl, name).lower()
|
return _re_snake.sub(repl, name).lower()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(camel_to_snake("HelloWorldEvent"))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.constants import BORDERS
|
from textual.constants import BORDERS
|
||||||
from textual.widgets import Button, Static
|
from textual.widgets import Button, Label
|
||||||
from textual.containers import Vertical
|
from textual.containers import Vertical
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class BorderApp(App):
|
|||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield BorderButtons()
|
yield BorderButtons()
|
||||||
self.text = Static(TEXT, id="text")
|
self.text = Label(TEXT, id="text")
|
||||||
yield self.text
|
yield self.text
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ ColorGroup.-active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ColorLabel {
|
Label {
|
||||||
padding: 0 0 1 0;
|
padding: 0 0 1 0;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
color: $text;
|
color: $text;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from textual.app import App, ComposeResult
|
|||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, Vertical
|
||||||
from textual.design import ColorSystem
|
from textual.design import ColorSystem
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Footer, Static
|
from textual.widgets import Button, Footer, Static, Label
|
||||||
|
|
||||||
|
|
||||||
class ColorButtons(Vertical):
|
class ColorButtons(Vertical):
|
||||||
@@ -28,10 +28,6 @@ class Content(Vertical):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ColorLabel(Static):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ColorsView(Vertical):
|
class ColorsView(Vertical):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
|
||||||
@@ -47,7 +43,7 @@ class ColorsView(Vertical):
|
|||||||
|
|
||||||
for color_name in ColorSystem.COLOR_NAMES:
|
for color_name in ColorSystem.COLOR_NAMES:
|
||||||
|
|
||||||
items: list[Widget] = [ColorLabel(f'"{color_name}"')]
|
items: list[Widget] = [Label(f'"{color_name}"')]
|
||||||
for level in LEVELS:
|
for level in LEVELS:
|
||||||
color = f"{color_name}-{level}" if level else color_name
|
color = f"{color_name}-{level}" if level else color_name
|
||||||
item = ColorItem(
|
item = ColorItem(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from textual.containers import Container, Horizontal, Vertical
|
|||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
from textual.scrollbar import ScrollBarRender
|
from textual.scrollbar import ScrollBarRender
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Footer, Static, Input
|
from textual.widgets import Button, Footer, Label, Input
|
||||||
|
|
||||||
VIRTUAL_SIZE = 100
|
VIRTUAL_SIZE = 100
|
||||||
WINDOW_SIZE = 10
|
WINDOW_SIZE = 10
|
||||||
@@ -27,7 +27,7 @@ class Bar(Widget):
|
|||||||
animation_running = Reactive(False)
|
animation_running = Reactive(False)
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
|
|
||||||
Bar {
|
Bar {
|
||||||
background: $surface;
|
background: $surface;
|
||||||
color: $error;
|
color: $error;
|
||||||
@@ -37,7 +37,7 @@ class Bar(Widget):
|
|||||||
background: $surface;
|
background: $surface;
|
||||||
color: $success;
|
color: $success;
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def watch_animation_running(self, running: bool) -> None:
|
def watch_animation_running(self, running: bool) -> None:
|
||||||
@@ -67,14 +67,14 @@ class EasingApp(App):
|
|||||||
self.animated_bar.position = START_POSITION
|
self.animated_bar.position = START_POSITION
|
||||||
duration_input = Input("1.0", placeholder="Duration", id="duration-input")
|
duration_input = Input("1.0", placeholder="Duration", id="duration-input")
|
||||||
|
|
||||||
self.opacity_widget = Static(
|
self.opacity_widget = Label(
|
||||||
f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget"
|
f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
yield EasingButtons()
|
yield EasingButtons()
|
||||||
yield Vertical(
|
yield Vertical(
|
||||||
Horizontal(
|
Horizontal(
|
||||||
Static("Animation Duration:", id="label"), duration_input, id="inputs"
|
Label("Animation Duration:", id="label"), duration_input, id="inputs"
|
||||||
),
|
),
|
||||||
Horizontal(
|
Horizontal(
|
||||||
self.animated_bar,
|
self.animated_bar,
|
||||||
|
|||||||
@@ -366,26 +366,3 @@ def parse(
|
|||||||
is_default_rules=is_default_rules,
|
is_default_rules=is_default_rules,
|
||||||
tie_breaker=tie_breaker,
|
tie_breaker=tie_breaker,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(parse_selectors("Foo > Bar.baz { foo: bar"))
|
|
||||||
|
|
||||||
css = """#something {
|
|
||||||
text: on red;
|
|
||||||
transition: offset 5.51s in_out_cubic;
|
|
||||||
offset-x: 100%;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
|
||||||
from rich.console import Console
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
stylesheet = Stylesheet()
|
|
||||||
try:
|
|
||||||
stylesheet.add_source(css)
|
|
||||||
except StylesheetParseError as e:
|
|
||||||
console.print(e.errors)
|
|
||||||
print(stylesheet)
|
|
||||||
print(stylesheet.css)
|
|
||||||
|
|||||||
@@ -356,16 +356,9 @@ class DOMQuery(Generic[QueryType]):
|
|||||||
Returns:
|
Returns:
|
||||||
AwaitRemove: An awaitable object that waits for the widgets to be removed.
|
AwaitRemove: An awaitable object that waits for the widgets to be removed.
|
||||||
"""
|
"""
|
||||||
prune_finished_event = asyncio.Event()
|
|
||||||
app = active_app.get()
|
app = active_app.get()
|
||||||
app.post_message_no_wait(
|
await_remove = app._remove_nodes(list(self))
|
||||||
events.Prune(
|
return await_remove
|
||||||
app,
|
|
||||||
widgets=app._detach_from_dom(list(self)),
|
|
||||||
finished_flag=prune_finished_event,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return AwaitRemove(prune_finished_event)
|
|
||||||
|
|
||||||
def set_styles(
|
def set_styles(
|
||||||
self, css: str | None = None, **update_styles
|
self, css: str | None = None, **update_styles
|
||||||
|
|||||||
@@ -383,10 +383,3 @@ def percentage_string_to_float(string: str) -> float:
|
|||||||
else:
|
else:
|
||||||
float_percentage = float(string)
|
float_percentage = float(string)
|
||||||
return float_percentage
|
return float_percentage
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(Scalar.parse("3.14fr"))
|
|
||||||
s = Scalar.parse("23")
|
|
||||||
print(repr(s))
|
|
||||||
print(repr(s.cells))
|
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ class StylesBase(ABC):
|
|||||||
class Styles(StylesBase):
|
class Styles(StylesBase):
|
||||||
node: DOMNode | None = None
|
node: DOMNode | None = None
|
||||||
_rules: RulesMap = field(default_factory=dict)
|
_rules: RulesMap = field(default_factory=dict)
|
||||||
|
_updates: int = 0
|
||||||
|
|
||||||
important: set[str] = field(default_factory=set)
|
important: set[str] = field(default_factory=set)
|
||||||
|
|
||||||
@@ -577,6 +578,7 @@ class Styles(StylesBase):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: ``True`` if a rule was cleared, or ``False`` if it was already not set.
|
bool: ``True`` if a rule was cleared, or ``False`` if it was already not set.
|
||||||
"""
|
"""
|
||||||
|
self._updates += 1
|
||||||
return self._rules.pop(rule, None) is not None
|
return self._rules.pop(rule, None) is not None
|
||||||
|
|
||||||
def get_rules(self) -> RulesMap:
|
def get_rules(self) -> RulesMap:
|
||||||
@@ -592,6 +594,7 @@ class Styles(StylesBase):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: ``True`` if the rule changed, otherwise ``False``.
|
bool: ``True`` if the rule changed, otherwise ``False``.
|
||||||
"""
|
"""
|
||||||
|
self._updates += 1
|
||||||
if value is None:
|
if value is None:
|
||||||
return self._rules.pop(rule, None) is not None
|
return self._rules.pop(rule, None) is not None
|
||||||
current = self._rules.get(rule)
|
current = self._rules.get(rule)
|
||||||
@@ -610,6 +613,7 @@ class Styles(StylesBase):
|
|||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset the rules to initial state."""
|
"""Reset the rules to initial state."""
|
||||||
|
self._updates += 1
|
||||||
self._rules.clear()
|
self._rules.clear()
|
||||||
|
|
||||||
def merge(self, other: Styles) -> None:
|
def merge(self, other: Styles) -> None:
|
||||||
@@ -618,10 +622,11 @@ class Styles(StylesBase):
|
|||||||
Args:
|
Args:
|
||||||
other (Styles): A Styles object.
|
other (Styles): A Styles object.
|
||||||
"""
|
"""
|
||||||
|
self._updates += 1
|
||||||
self._rules.update(other._rules)
|
self._rules.update(other._rules)
|
||||||
|
|
||||||
def merge_rules(self, rules: RulesMap) -> None:
|
def merge_rules(self, rules: RulesMap) -> None:
|
||||||
|
self._updates += 1
|
||||||
self._rules.update(rules)
|
self._rules.update(rules)
|
||||||
|
|
||||||
def extract_rules(
|
def extract_rules(
|
||||||
@@ -929,6 +934,18 @@ class RenderStyles(StylesBase):
|
|||||||
self._base_styles = base
|
self._base_styles = base
|
||||||
self._inline_styles = inline_styles
|
self._inline_styles = inline_styles
|
||||||
self._animate: BoundAnimator | None = None
|
self._animate: BoundAnimator | None = None
|
||||||
|
self._updates: int = 0
|
||||||
|
self._rich_style: tuple[int, Style] | None = None
|
||||||
|
self._gutter: tuple[int, Spacing] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cache_key(self) -> int:
|
||||||
|
"""A key key, that changes when any style is changed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: An opaque integer.
|
||||||
|
"""
|
||||||
|
return self._updates + self._base_styles._updates + self._inline_styles._updates
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self) -> Styles:
|
def base(self) -> Styles:
|
||||||
@@ -946,6 +963,21 @@ class RenderStyles(StylesBase):
|
|||||||
assert self.node is not None
|
assert self.node is not None
|
||||||
return self.node.rich_style
|
return self.node.rich_style
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gutter(self) -> Spacing:
|
||||||
|
"""Get space around widget.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Spacing: Space around widget content.
|
||||||
|
"""
|
||||||
|
if self._gutter is not None:
|
||||||
|
cache_key, gutter = self._gutter
|
||||||
|
if cache_key == self._updates:
|
||||||
|
return gutter
|
||||||
|
gutter = self.padding + self.border.spacing
|
||||||
|
self._gutter = (self._cache_key, gutter)
|
||||||
|
return gutter
|
||||||
|
|
||||||
def animate(
|
def animate(
|
||||||
self,
|
self,
|
||||||
attribute: str,
|
attribute: str,
|
||||||
@@ -972,6 +1004,7 @@ class RenderStyles(StylesBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if self._animate is None:
|
if self._animate is None:
|
||||||
|
assert self.node is not None
|
||||||
self._animate = self.node.app.animator.bind(self)
|
self._animate = self.node.app.animator.bind(self)
|
||||||
assert self._animate is not None
|
assert self._animate is not None
|
||||||
self._animate(
|
self._animate(
|
||||||
@@ -1003,16 +1036,19 @@ class RenderStyles(StylesBase):
|
|||||||
|
|
||||||
def merge_rules(self, rules: RulesMap) -> None:
|
def merge_rules(self, rules: RulesMap) -> None:
|
||||||
self._inline_styles.merge_rules(rules)
|
self._inline_styles.merge_rules(rules)
|
||||||
|
self._updates += 1
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset the rules to initial state."""
|
"""Reset the rules to initial state."""
|
||||||
self._inline_styles.reset()
|
self._inline_styles.reset()
|
||||||
|
self._updates += 1
|
||||||
|
|
||||||
def has_rule(self, rule: str) -> bool:
|
def has_rule(self, rule: str) -> bool:
|
||||||
"""Check if a rule has been set."""
|
"""Check if a rule has been set."""
|
||||||
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)
|
return self._inline_styles.has_rule(rule) or self._base_styles.has_rule(rule)
|
||||||
|
|
||||||
def set_rule(self, rule: str, value: object | None) -> bool:
|
def set_rule(self, rule: str, value: object | None) -> bool:
|
||||||
|
self._updates += 1
|
||||||
return self._inline_styles.set_rule(rule, value)
|
return self._inline_styles.set_rule(rule, value)
|
||||||
|
|
||||||
def get_rule(self, rule: str, default: object = None) -> object:
|
def get_rule(self, rule: str, default: object = None) -> object:
|
||||||
@@ -1022,6 +1058,7 @@ class RenderStyles(StylesBase):
|
|||||||
|
|
||||||
def clear_rule(self, rule_name: str) -> bool:
|
def clear_rule(self, rule_name: str) -> bool:
|
||||||
"""Clear a rule (from inline)."""
|
"""Clear a rule (from inline)."""
|
||||||
|
self._updates += 1
|
||||||
return self._inline_styles.clear_rule(rule_name)
|
return self._inline_styles.clear_rule(rule_name)
|
||||||
|
|
||||||
def get_rules(self) -> RulesMap:
|
def get_rules(self) -> RulesMap:
|
||||||
@@ -1037,25 +1074,3 @@ class RenderStyles(StylesBase):
|
|||||||
styles.merge(self._inline_styles)
|
styles.merge(self._inline_styles)
|
||||||
combined_css = styles.css
|
combined_css = styles.css
|
||||||
return combined_css
|
return combined_css
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
styles = Styles()
|
|
||||||
|
|
||||||
styles.display = "none"
|
|
||||||
styles.visibility = "hidden"
|
|
||||||
styles.border = ("solid", "rgb(10,20,30)")
|
|
||||||
styles.outline_right = ("solid", "red")
|
|
||||||
styles.text_style = "italic"
|
|
||||||
styles.dock = "bar"
|
|
||||||
styles.layers = "foo bar"
|
|
||||||
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
print(styles.text_style)
|
|
||||||
print(styles.text)
|
|
||||||
|
|
||||||
print(styles)
|
|
||||||
print(styles.css)
|
|
||||||
|
|
||||||
print(styles.extract_rules((0, 1, 0)))
|
|
||||||
|
|||||||
@@ -197,18 +197,3 @@ def tokenize_values(values: dict[str, str]) -> dict[str, list[Token]]:
|
|||||||
name: list(tokenize_value(value, "__name__")) for name, value in values.items()
|
name: list(tokenize_value(value, "__name__")) for name, value in values.items()
|
||||||
}
|
}
|
||||||
return value_tokens
|
return value_tokens
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
css = """#something {
|
|
||||||
|
|
||||||
color: rgb(10,12,23)
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
# transition: offset 500 in_out_cubic;
|
|
||||||
tokens = tokenize(css, __name__)
|
|
||||||
print(list(tokens))
|
|
||||||
|
|
||||||
print(tokenize_values({"primary": "rgb(10,20,30)", "secondary": "#ff00ff"}))
|
|
||||||
|
|||||||
@@ -222,11 +222,3 @@ def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
|
|||||||
table.add_column("Dark", justify="center")
|
table.add_column("Dark", justify="center")
|
||||||
table.add_row(make_shades(light), make_shades(dark))
|
table.add_row(make_shades(light), make_shades(dark))
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from .app import DEFAULT_COLORS
|
|
||||||
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
print(show_design(DEFAULT_COLORS["light"], DEFAULT_COLORS["dark"]))
|
|
||||||
|
|||||||
@@ -236,17 +236,3 @@ class LinuxDriver(Driver):
|
|||||||
finally:
|
finally:
|
||||||
with timer("selector.close"):
|
with timer("selector.close"):
|
||||||
selector.close()
|
selector.close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from rich.console import Console
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
from ..app import App
|
|
||||||
|
|
||||||
class MyApp(App):
|
|
||||||
async def on_mount(self, event: events.Mount) -> None:
|
|
||||||
self.set_timer(5, callback=self._close_messages)
|
|
||||||
|
|
||||||
MyApp.run()
|
|
||||||
|
|||||||
@@ -127,28 +127,6 @@ class Unmount(Mount, bubble=False, verbose=False):
|
|||||||
"""Sent when a widget is unmounted and may not longer receive messages."""
|
"""Sent when a widget is unmounted and may not longer receive messages."""
|
||||||
|
|
||||||
|
|
||||||
class Prune(Event, bubble=False):
|
|
||||||
"""Sent to the app to ask it to prune one or more widgets from the DOM.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
widgets (list[Widgets]): The list of widgets to prune.
|
|
||||||
finished_flag (asyncio.Event): An asyncio Event to that will be flagged when the prune is done.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, sender: MessageTarget, widgets: list[Widget], finished_flag: asyncio.Event
|
|
||||||
) -> None:
|
|
||||||
"""Initialise the event.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
widgets (list[Widgets]): The list of widgets to prune.
|
|
||||||
finished_flag (asyncio.Event): An asyncio Event to that will be flagged when the prune is done.
|
|
||||||
"""
|
|
||||||
super().__init__(sender)
|
|
||||||
self.finished_flag = finished_flag
|
|
||||||
self.widgets = widgets
|
|
||||||
|
|
||||||
|
|
||||||
class Show(Event, bubble=False):
|
class Show(Event, bubble=False):
|
||||||
"""Sent when a widget has become visible."""
|
"""Sent when a widget has become visible."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unicodedata
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
@@ -219,7 +220,34 @@ KEY_ALIASES = {
|
|||||||
"ctrl+j": ["newline"],
|
"ctrl+j": ["newline"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KEY_DISPLAY_ALIASES = {
|
||||||
|
"up": "↑",
|
||||||
|
"down": "↓",
|
||||||
|
"left": "←",
|
||||||
|
"right": "→",
|
||||||
|
"backspace": "⌫",
|
||||||
|
"escape": "ESC",
|
||||||
|
"enter": "⏎",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_key_aliases(key: str) -> list[str]:
|
def _get_key_aliases(key: str) -> list[str]:
|
||||||
"""Return all aliases for the given key, including the key itself"""
|
"""Return all aliases for the given key, including the key itself"""
|
||||||
return [key] + KEY_ALIASES.get(key, [])
|
return [key] + KEY_ALIASES.get(key, [])
|
||||||
|
|
||||||
|
|
||||||
|
def _get_key_display(key: str) -> str:
|
||||||
|
"""Given a key (i.e. the `key` string argument to Binding __init__),
|
||||||
|
return the value that should be displayed in the app when referring
|
||||||
|
to this key (e.g. in the Footer widget)."""
|
||||||
|
display_alias = KEY_DISPLAY_ALIASES.get(key)
|
||||||
|
if display_alias:
|
||||||
|
return display_alias
|
||||||
|
|
||||||
|
original_key = REPLACED_KEYS.get(key, key)
|
||||||
|
try:
|
||||||
|
unicode_character = unicodedata.lookup(original_key.upper().replace("_", " "))
|
||||||
|
except KeyError:
|
||||||
|
return original_key.upper()
|
||||||
|
|
||||||
|
return unicode_character
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Reactive(Generic[ReactiveType]):
|
|||||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||||
init (bool, optional): Call watchers on initialize (post mount). Defaults to False.
|
init (bool, optional): Call watchers on initialize (post mount). Defaults to False.
|
||||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
always_update (bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -76,7 +76,7 @@ class Reactive(Generic[ReactiveType]):
|
|||||||
default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
|
default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
|
||||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
always_update (bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Reactive: A Reactive instance which calls watchers or initialize.
|
Reactive: A Reactive instance which calls watchers or initialize.
|
||||||
@@ -292,7 +292,7 @@ class reactive(Reactive[ReactiveType]):
|
|||||||
layout (bool, optional): Perform a layout on change. Defaults to False.
|
layout (bool, optional): Perform a layout on change. Defaults to False.
|
||||||
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
repaint (bool, optional): Perform a repaint on change. Defaults to True.
|
||||||
init (bool, optional): Call watchers on initialize (post mount). Defaults to True.
|
init (bool, optional): Call watchers on initialize (post mount). Defaults to True.
|
||||||
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
always_update (bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@@ -25,9 +25,3 @@ class Blank:
|
|||||||
for _ in range(height):
|
for _ in range(height):
|
||||||
yield segment
|
yield segment
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
print(Blank("red"))
|
|
||||||
|
|||||||
@@ -36,9 +36,3 @@ class VerticalGradient:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
yield Segment(f"{width * ' '}\n", line_color)
|
yield Segment(f"{width * ' '}\n", line_color)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from rich import print
|
|
||||||
|
|
||||||
print(VerticalGradient("red", "blue"))
|
|
||||||
|
|||||||
@@ -310,18 +310,19 @@ class Screen(Widget):
|
|||||||
# Check for any widgets marked as 'dirty' (needs a repaint)
|
# Check for any widgets marked as 'dirty' (needs a repaint)
|
||||||
event.prevent_default()
|
event.prevent_default()
|
||||||
|
|
||||||
if self.is_current:
|
async with self.app._dom_lock:
|
||||||
if self._layout_required:
|
if self.is_current:
|
||||||
self._refresh_layout()
|
if self._layout_required:
|
||||||
self._layout_required = False
|
self._refresh_layout()
|
||||||
self._dirty_widgets.clear()
|
self._layout_required = False
|
||||||
if self._repaint_required:
|
self._dirty_widgets.clear()
|
||||||
self._dirty_widgets.clear()
|
if self._repaint_required:
|
||||||
self._dirty_widgets.add(self)
|
self._dirty_widgets.clear()
|
||||||
self._repaint_required = False
|
self._dirty_widgets.add(self)
|
||||||
|
self._repaint_required = False
|
||||||
|
|
||||||
if self._dirty_widgets:
|
if self._dirty_widgets:
|
||||||
self.update_timer.resume()
|
self.update_timer.resume()
|
||||||
|
|
||||||
# The Screen is idle - a good opportunity to invoke the scheduled callbacks
|
# The Screen is idle - a good opportunity to invoke the scheduled callbacks
|
||||||
await self._invoke_and_clear_callbacks()
|
await self._invoke_and_clear_callbacks()
|
||||||
|
|||||||
@@ -320,18 +320,3 @@ class ScrollBarCorner(Widget):
|
|||||||
styles = self.parent.styles
|
styles = self.parent.styles
|
||||||
color = styles.scrollbar_corner_color
|
color = styles.scrollbar_corner_color
|
||||||
return Blank(color)
|
return Blank(color)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from rich.console import Console
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
thickness = 2
|
|
||||||
console.print(f"Bars thickness: {thickness}")
|
|
||||||
|
|
||||||
console.print("Vertical bar:")
|
|
||||||
console.print(ScrollBarRender.render_bar(thickness=thickness))
|
|
||||||
|
|
||||||
console.print("Horizontal bar:")
|
|
||||||
console.print(ScrollBarRender.render_bar(vertical=False, thickness=thickness))
|
|
||||||
|
|||||||
@@ -523,15 +523,19 @@ class Widget(DOMNode):
|
|||||||
|
|
||||||
# Decide the final resting place depending on what we've been asked
|
# Decide the final resting place depending on what we've been asked
|
||||||
# to do.
|
# to do.
|
||||||
|
insert_before: int | None = None
|
||||||
|
insert_after: int | None = None
|
||||||
if before is not None:
|
if before is not None:
|
||||||
parent, before = self._find_mount_point(before)
|
parent, insert_before = self._find_mount_point(before)
|
||||||
elif after is not None:
|
elif after is not None:
|
||||||
parent, after = self._find_mount_point(after)
|
parent, insert_after = self._find_mount_point(after)
|
||||||
else:
|
else:
|
||||||
parent = self
|
parent = self
|
||||||
|
|
||||||
return AwaitMount(
|
return AwaitMount(
|
||||||
self.app._register(parent, *widgets, before=before, after=after)
|
self.app._register(
|
||||||
|
parent, *widgets, before=insert_before, after=insert_after
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def move_child(
|
def move_child(
|
||||||
@@ -560,7 +564,7 @@ class Widget(DOMNode):
|
|||||||
if before is None and after is None:
|
if before is None and after is None:
|
||||||
raise WidgetError("One of `before` or `after` is required.")
|
raise WidgetError("One of `before` or `after` is required.")
|
||||||
elif before is not None and after is not None:
|
elif before is not None and after is not None:
|
||||||
raise WidgetError("Only one of `before`or `after` can be handled.")
|
raise WidgetError("Only one of `before` or `after` can be handled.")
|
||||||
|
|
||||||
def _to_widget(child: int | Widget, called: str) -> Widget:
|
def _to_widget(child: int | Widget, called: str) -> Widget:
|
||||||
"""Ensure a given child reference is a Widget."""
|
"""Ensure a given child reference is a Widget."""
|
||||||
@@ -697,7 +701,6 @@ class Widget(DOMNode):
|
|||||||
Returns:
|
Returns:
|
||||||
int: The height of the content.
|
int: The height of the content.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.is_container:
|
if self.is_container:
|
||||||
assert self._layout is not None
|
assert self._layout is not None
|
||||||
height = (
|
height = (
|
||||||
@@ -2114,15 +2117,9 @@ class Widget(DOMNode):
|
|||||||
Returns:
|
Returns:
|
||||||
AwaitRemove: An awaitable object that waits for the widget to be removed.
|
AwaitRemove: An awaitable object that waits for the widget to be removed.
|
||||||
"""
|
"""
|
||||||
prune_finished_event = AsyncEvent()
|
|
||||||
self.app.post_message_no_wait(
|
await_remove = self.app._remove_nodes([self])
|
||||||
events.Prune(
|
return await_remove
|
||||||
self,
|
|
||||||
widgets=self.app._detach_from_dom([self]),
|
|
||||||
finished_flag=prune_finished_event,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return AwaitRemove(prune_finished_event)
|
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
def render(self) -> RenderableType:
|
||||||
"""Get renderable for widget.
|
"""Get renderable for widget.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ if typing.TYPE_CHECKING:
|
|||||||
from ._directory_tree import DirectoryTree
|
from ._directory_tree import DirectoryTree
|
||||||
from ._footer import Footer
|
from ._footer import Footer
|
||||||
from ._header import Header
|
from ._header import Header
|
||||||
|
from ._label import Label
|
||||||
from ._list_view import ListView
|
from ._list_view import ListView
|
||||||
from ._list_item import ListItem
|
from ._list_item import ListItem
|
||||||
from ._pretty import Pretty
|
from ._pretty import Pretty
|
||||||
@@ -34,6 +35,7 @@ __all__ = [
|
|||||||
"Header",
|
"Header",
|
||||||
"ListItem",
|
"ListItem",
|
||||||
"ListView",
|
"ListView",
|
||||||
|
"Label",
|
||||||
"Placeholder",
|
"Placeholder",
|
||||||
"Pretty",
|
"Pretty",
|
||||||
"Static",
|
"Static",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from ._checkbox import Checkbox as Checkbox
|
|||||||
from ._directory_tree import DirectoryTree as DirectoryTree
|
from ._directory_tree import DirectoryTree as DirectoryTree
|
||||||
from ._footer import Footer as Footer
|
from ._footer import Footer as Footer
|
||||||
from ._header import Header as Header
|
from ._header import Header as Header
|
||||||
|
from ._label import Label as Label
|
||||||
from ._list_view import ListView as ListView
|
from ._list_view import ListView as ListView
|
||||||
from ._list_item import ListItem as ListItem
|
from ._list_item import ListItem as ListItem
|
||||||
from ._placeholder import Placeholder as Placeholder
|
from ._placeholder import Placeholder as Placeholder
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ class Button(Static, can_focus=True):
|
|||||||
"""Create a Button widget.
|
"""Create a Button widget.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label (str): The text that appears within the button.
|
label (str, optional): The text that appears within the button.
|
||||||
disabled (bool): Whether the button is disabled or not.
|
disabled (bool, optional): Whether the button is disabled or not.
|
||||||
variant (ButtonVariant): The variant of the button.
|
variant (ButtonVariant, optional): The variant of the button.
|
||||||
name (str | None, optional): The name of the button.
|
name (str | None, optional): The name of the button.
|
||||||
id (str | None, optional): The ID of the button in the DOM.
|
id (str | None, optional): The ID of the button in the DOM.
|
||||||
classes (str | None, optional): The CSS classes of the button.
|
classes (str | None, optional): The CSS classes of the button.
|
||||||
@@ -186,7 +186,7 @@ class Button(Static, can_focus=True):
|
|||||||
if disabled:
|
if disabled:
|
||||||
self.add_class("-disabled")
|
self.add_class("-disabled")
|
||||||
|
|
||||||
self.variant = variant
|
self.variant = self.validate_variant(variant)
|
||||||
|
|
||||||
label: Reactive[RenderableType] = Reactive("")
|
label: Reactive[RenderableType] = Reactive("")
|
||||||
variant = Reactive.init("default")
|
variant = Reactive.init("default")
|
||||||
@@ -267,8 +267,8 @@ class Button(Static, can_focus=True):
|
|||||||
"""Utility constructor for creating a success Button variant.
|
"""Utility constructor for creating a success Button variant.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label (str): The text that appears within the button.
|
label (str, optional): The text that appears within the button.
|
||||||
disabled (bool): Whether the button is disabled or not.
|
disabled (bool, optional): Whether the button is disabled or not.
|
||||||
name (str | None, optional): The name of the button.
|
name (str | None, optional): The name of the button.
|
||||||
id (str | None, optional): The ID of the button in the DOM.
|
id (str | None, optional): The ID of the button in the DOM.
|
||||||
classes(str | None, optional): The CSS classes of the button.
|
classes(str | None, optional): The CSS classes of the button.
|
||||||
@@ -298,8 +298,8 @@ class Button(Static, can_focus=True):
|
|||||||
"""Utility constructor for creating a warning Button variant.
|
"""Utility constructor for creating a warning Button variant.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label (str): The text that appears within the button.
|
label (str, optional): The text that appears within the button.
|
||||||
disabled (bool): Whether the button is disabled or not.
|
disabled (bool, optional): Whether the button is disabled or not.
|
||||||
name (str | None, optional): The name of the button.
|
name (str | None, optional): The name of the button.
|
||||||
id (str | None, optional): The ID of the button in the DOM.
|
id (str | None, optional): The ID of the button in the DOM.
|
||||||
classes (str | None, optional): The CSS classes of the button.
|
classes (str | None, optional): The CSS classes of the button.
|
||||||
@@ -329,8 +329,8 @@ class Button(Static, can_focus=True):
|
|||||||
"""Utility constructor for creating an error Button variant.
|
"""Utility constructor for creating an error Button variant.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label (str): The text that appears within the button.
|
label (str, optional): The text that appears within the button.
|
||||||
disabled (bool): Whether the button is disabled or not.
|
disabled (bool, optional): Whether the button is disabled or not.
|
||||||
name (str | None, optional): The name of the button.
|
name (str | None, optional): The name of the button.
|
||||||
id (str | None, optional): The ID of the button in the DOM.
|
id (str | None, optional): The ID of the button in the DOM.
|
||||||
classes (str | None, optional): The CSS classes of the button.
|
classes (str | None, optional): The CSS classes of the button.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from rich.console import RenderableType
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
|
from ..keys import _get_key_display
|
||||||
from ..reactive import Reactive, watch
|
from ..reactive import Reactive, watch
|
||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
|
|
||||||
@@ -99,11 +100,12 @@ class Footer(Widget):
|
|||||||
|
|
||||||
for action, bindings in action_to_bindings.items():
|
for action, bindings in action_to_bindings.items():
|
||||||
binding = bindings[0]
|
binding = bindings[0]
|
||||||
key_display = (
|
if binding.key_display is None:
|
||||||
binding.key.upper()
|
key_display = self.app.get_key_display(binding.key)
|
||||||
if binding.key_display is None
|
if key_display is None:
|
||||||
else binding.key_display
|
key_display = binding.key.upper()
|
||||||
)
|
else:
|
||||||
|
key_display = binding.key_display
|
||||||
hovered = self.highlight_key == binding.key
|
hovered = self.highlight_key == binding.key
|
||||||
key_text = Text.assemble(
|
key_text = Text.assemble(
|
||||||
(f" {key_display} ", highlight_key_style if hovered else key_style),
|
(f" {key_display} ", highlight_key_style if hovered else key_style),
|
||||||
|
|||||||
@@ -176,6 +176,10 @@ class Input(Widget, can_focus=True):
|
|||||||
if self.has_focus:
|
if self.has_focus:
|
||||||
cursor_style = self.get_component_rich_style("input--cursor")
|
cursor_style = self.get_component_rich_style("input--cursor")
|
||||||
if self._cursor_visible:
|
if self._cursor_visible:
|
||||||
|
# If the placeholder is empty, there's no characters to stylise
|
||||||
|
# to make the cursor flash, so use a single space character
|
||||||
|
if len(placeholder) == 0:
|
||||||
|
placeholder = Text(" ")
|
||||||
placeholder.stylize(cursor_style, 0, 1)
|
placeholder.stylize(cursor_style, 0, 1)
|
||||||
return placeholder
|
return placeholder
|
||||||
return _InputRenderable(self, self._cursor_visible)
|
return _InputRenderable(self, self._cursor_visible)
|
||||||
|
|||||||
7
src/textual/widgets/_label.py
Normal file
7
src/textual/widgets/_label.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""Provides a simple Label widget."""
|
||||||
|
|
||||||
|
from ._static import Static
|
||||||
|
|
||||||
|
|
||||||
|
class Label(Static):
|
||||||
|
"""A simple label widget for displaying text-oriented renderables."""
|
||||||
@@ -177,16 +177,16 @@ class Tabs(Widget):
|
|||||||
Args:
|
Args:
|
||||||
tabs (list[Tab]): A list of Tab objects defining the tabs which should be rendered.
|
tabs (list[Tab]): A list of Tab objects defining the tabs which should be rendered.
|
||||||
active_tab (str, optional): The name of the tab that should be active on first render.
|
active_tab (str, optional): The name of the tab that should be active on first render.
|
||||||
active_tab_style (StyleType): Style to apply to the label of the active tab.
|
active_tab_style (StyleType, optional): Style to apply to the label of the active tab.
|
||||||
active_bar_style (StyleType): Style to apply to the underline of the active tab.
|
active_bar_style (StyleType, optional): Style to apply to the underline of the active tab.
|
||||||
inactive_tab_style (StyleType): Style to apply to the label of inactive tabs.
|
inactive_tab_style (StyleType, optional): Style to apply to the label of inactive tabs.
|
||||||
inactive_bar_style (StyleType): Style to apply to the underline of inactive tabs.
|
inactive_bar_style (StyleType, optional): Style to apply to the underline of inactive tabs.
|
||||||
inactive_text_opacity (float): Opacity of the text labels of inactive tabs.
|
inactive_text_opacity (float, optional): Opacity of the text labels of inactive tabs.
|
||||||
animation_duration (float): The duration of the tab change animation, in seconds.
|
animation_duration (float, optional): The duration of the tab change animation, in seconds.
|
||||||
animation_function (str): The easing function to use for the tab change animation.
|
animation_function (str, optional): The easing function to use for the tab change animation.
|
||||||
tab_padding (int, optional): The padding at the side of each tab. If None, tabs will
|
tab_padding (int, optional): The padding at the side of each tab. If None, tabs will
|
||||||
automatically be padded such that they fit the available horizontal space.
|
automatically be padded such that they fit the available horizontal space.
|
||||||
search_by_first_character (bool): If True, entering a character on your keyboard
|
search_by_first_character (bool, optional): If True, entering a character on your keyboard
|
||||||
will activate the next tab (in left-to-right order) with a label starting with
|
will activate the next tab (in left-to-right order) with a label starting with
|
||||||
that character.
|
that character.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -6166,6 +6166,163 @@
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_key_display
|
||||||
|
'''
|
||||||
|
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Generated with Rich https://www.textualize.io -->
|
||||||
|
<style>
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fira Code";
|
||||||
|
src: local("FiraCode-Regular"),
|
||||||
|
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
|
||||||
|
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fira Code";
|
||||||
|
src: local("FiraCode-Bold"),
|
||||||
|
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
|
||||||
|
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
|
||||||
|
font-style: bold;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-1765381587-matrix {
|
||||||
|
font-family: Fira Code, monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24.4px;
|
||||||
|
font-variant-east-asian: full-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-1765381587-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-1765381587-r1 { fill: #e1e1e1 }
|
||||||
|
.terminal-1765381587-r2 { fill: #c5c8c6 }
|
||||||
|
.terminal-1765381587-r3 { fill: #dde8f3;font-weight: bold }
|
||||||
|
.terminal-1765381587-r4 { fill: #ddedf9 }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<clipPath id="terminal-1765381587-clip-terminal">
|
||||||
|
<rect x="0" y="0" width="975.0" height="584.5999999999999" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-0">
|
||||||
|
<rect x="0" y="1.5" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-1">
|
||||||
|
<rect x="0" y="25.9" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-2">
|
||||||
|
<rect x="0" y="50.3" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-3">
|
||||||
|
<rect x="0" y="74.7" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-4">
|
||||||
|
<rect x="0" y="99.1" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-5">
|
||||||
|
<rect x="0" y="123.5" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-6">
|
||||||
|
<rect x="0" y="147.9" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-7">
|
||||||
|
<rect x="0" y="172.3" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-8">
|
||||||
|
<rect x="0" y="196.7" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-9">
|
||||||
|
<rect x="0" y="221.1" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-10">
|
||||||
|
<rect x="0" y="245.5" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-11">
|
||||||
|
<rect x="0" y="269.9" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-12">
|
||||||
|
<rect x="0" y="294.3" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-13">
|
||||||
|
<rect x="0" y="318.7" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-14">
|
||||||
|
<rect x="0" y="343.1" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-15">
|
||||||
|
<rect x="0" y="367.5" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-16">
|
||||||
|
<rect x="0" y="391.9" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-17">
|
||||||
|
<rect x="0" y="416.3" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-18">
|
||||||
|
<rect x="0" y="440.7" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-19">
|
||||||
|
<rect x="0" y="465.1" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-20">
|
||||||
|
<rect x="0" y="489.5" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-21">
|
||||||
|
<rect x="0" y="513.9" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="terminal-1765381587-line-22">
|
||||||
|
<rect x="0" y="538.3" width="976" height="24.65"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-1765381587-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">KeyDisplayApp</text>
|
||||||
|
<g transform="translate(26,22)">
|
||||||
|
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||||
|
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||||
|
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(9, 41)" clip-path="url(#terminal-1765381587-clip-terminal)">
|
||||||
|
<rect fill="#1e1e1e" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="0" y="562.7" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="36.6" y="562.7" width="122" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="158.6" y="562.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="207.4" y="562.7" width="122" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="329.4" y="562.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="439.2" y="562.7" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="536.8" y="562.7" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="573.4" y="562.7" width="122" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="695.4" y="562.7" width="280.6" height="24.65" shape-rendering="crispEdges"/>
|
||||||
|
<g class="terminal-1765381587-matrix">
|
||||||
|
<text class="terminal-1765381587-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-1765381587-line-0)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-1765381587-line-1)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-1765381587-line-2)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-1765381587-line-3)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-1765381587-line-4)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-1765381587-line-5)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-1765381587-line-6)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-1765381587-line-7)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-1765381587-line-8)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-1765381587-line-9)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-1765381587-line-10)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-1765381587-line-11)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-1765381587-line-12)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-1765381587-line-13)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-1765381587-line-14)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-1765381587-line-15)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-1765381587-line-16)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-1765381587-line-17)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-1765381587-line-18)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-1765381587-line-19)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-1765381587-line-20)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-1765381587-line-21)">
|
||||||
|
</text><text class="terminal-1765381587-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-1765381587-line-22)">
|
||||||
|
</text><text class="terminal-1765381587-r3" x="0" y="581.2" textLength="36.6" clip-path="url(#terminal-1765381587-line-23)"> ? </text><text class="terminal-1765381587-r4" x="36.6" y="581.2" textLength="122" clip-path="url(#terminal-1765381587-line-23)"> Question </text><text class="terminal-1765381587-r3" x="158.6" y="581.2" textLength="48.8" clip-path="url(#terminal-1765381587-line-23)"> ^q </text><text class="terminal-1765381587-r4" x="207.4" y="581.2" textLength="122" clip-path="url(#terminal-1765381587-line-23)"> Quit app </text><text class="terminal-1765381587-r3" x="329.4" y="581.2" textLength="109.8" clip-path="url(#terminal-1765381587-line-23)"> Escape! </text><text class="terminal-1765381587-r4" x="439.2" y="581.2" textLength="97.6" clip-path="url(#terminal-1765381587-line-23)"> Escape </text><text class="terminal-1765381587-r3" x="536.8" y="581.2" textLength="36.6" clip-path="url(#terminal-1765381587-line-23)"> A </text><text class="terminal-1765381587-r4" x="573.4" y="581.2" textLength="122" clip-path="url(#terminal-1765381587-line-23)"> Letter A </text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
'''
|
||||||
|
# ---
|
||||||
# name: test_layers
|
# name: test_layers
|
||||||
'''
|
'''
|
||||||
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
|
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
36
tests/snapshot_tests/snapshot_apps/key_display.py
Normal file
36
tests/snapshot_tests/snapshot_apps/key_display.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.binding import Binding
|
||||||
|
from textual.widgets import Footer
|
||||||
|
|
||||||
|
|
||||||
|
class KeyDisplayApp(App):
|
||||||
|
"""Tests how keys are displayed in the Footer, and ensures
|
||||||
|
that overriding the key_displays works as expected.
|
||||||
|
Exercises both the built-in Textual key display replacements,
|
||||||
|
and user supplied replacements.
|
||||||
|
Will break when we update the Footer - but we should add a similar
|
||||||
|
test (or updated snapshot) for the updated Footer."""
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("question_mark", "question", "Question"),
|
||||||
|
Binding("ctrl+q", "quit", "Quit app"),
|
||||||
|
Binding("escape", "escape", "Escape"),
|
||||||
|
Binding("a", "a", "Letter A"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
|
def get_key_display(self, key: str) -> str:
|
||||||
|
key_display_replacements = {
|
||||||
|
"escape": "Escape!",
|
||||||
|
"ctrl+q": "^q",
|
||||||
|
}
|
||||||
|
display = key_display_replacements.get(key)
|
||||||
|
if display:
|
||||||
|
return display
|
||||||
|
return super().get_key_display(key)
|
||||||
|
|
||||||
|
|
||||||
|
app = KeyDisplayApp()
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
||||||
@@ -118,3 +118,9 @@ def test_css_property(file_name, snap_compare):
|
|||||||
def test_multiple_css(snap_compare):
|
def test_multiple_css(snap_compare):
|
||||||
# Interaction between multiple CSS files and app-level/classvar CSS
|
# Interaction between multiple CSS files and app-level/classvar CSS
|
||||||
assert snap_compare("snapshot_apps/multiple_css/multiple_css.py")
|
assert snap_compare("snapshot_apps/multiple_css/multiple_css.py")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Other ---
|
||||||
|
|
||||||
|
def test_key_display(snap_compare):
|
||||||
|
assert snap_compare(SNAPSHOT_APPS_DIR / "key_display.py")
|
||||||
|
|||||||
@@ -11,8 +11,37 @@ skip_py310 = pytest.mark.skipif(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_installed_screens():
|
||||||
|
class ScreensApp(App):
|
||||||
|
SCREENS = {
|
||||||
|
"home": Screen, # Screen type
|
||||||
|
"one": Screen(), # Screen instance
|
||||||
|
"two": lambda: Screen() # Callable[[], Screen]
|
||||||
|
}
|
||||||
|
|
||||||
|
app = ScreensApp()
|
||||||
|
async with app.run_test() as pilot:
|
||||||
|
pilot.app.push_screen("home") # Instantiates and pushes the "home" screen
|
||||||
|
pilot.app.push_screen("one") # Pushes the pre-instantiated "one" screen
|
||||||
|
pilot.app.push_screen("home") # Pushes the single instance of "home" screen
|
||||||
|
pilot.app.push_screen("two") # Calls the callable, pushes returned Screen instance
|
||||||
|
|
||||||
|
assert len(app.screen_stack) == 5
|
||||||
|
assert app.screen_stack[1] is app.screen_stack[3]
|
||||||
|
assert app.screen is app.screen_stack[4]
|
||||||
|
assert isinstance(app.screen, Screen)
|
||||||
|
assert app.is_screen_installed(app.screen)
|
||||||
|
|
||||||
|
assert pilot.app.pop_screen()
|
||||||
|
assert pilot.app.pop_screen()
|
||||||
|
assert pilot.app.pop_screen()
|
||||||
|
assert pilot.app.pop_screen()
|
||||||
|
with pytest.raises(ScreenStackError):
|
||||||
|
pilot.app.pop_screen()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@skip_py310
|
@skip_py310
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_screens():
|
async def test_screens():
|
||||||
|
|
||||||
app = App()
|
app = App()
|
||||||
|
|||||||
Reference in New Issue
Block a user