diff --git a/docs/examples/actions/colorizer.py b/docs/examples/actions/colorizer.py deleted file mode 100644 index f0c3cc0f0..000000000 --- a/docs/examples/actions/colorizer.py +++ /dev/null @@ -1,14 +0,0 @@ -from textual.app import App - - -class Colorizer(App): - async def on_load(self): - await self.bind("r", "color('red')") - await self.bind("g", "color('green')") - await self.bind("b", "color('blue')") - - def action_color(self, color: str) -> None: - self.background = f"on {color}" - - -Colorizer.run() diff --git a/docs/examples/actions/quiter.py b/docs/examples/actions/quiter.py deleted file mode 100644 index 53ff19356..000000000 --- a/docs/examples/actions/quiter.py +++ /dev/null @@ -1,9 +0,0 @@ -from textual.app import App - - -class Quiter(App): - async def on_load(self): - await self.bind("q", "quit") - - -Quiter.run() diff --git a/docs/examples/messages_and_events/beep.py b/docs/examples/messages_and_events/beep.py deleted file mode 100644 index 7cbd54de0..000000000 --- a/docs/examples/messages_and_events/beep.py +++ /dev/null @@ -1,9 +0,0 @@ -from textual.app import App - - -class Beeper(App): - def on_key(self): - self.console.bell() - - -Beeper.run() diff --git a/docs/examples/messages_and_events/beep_typed.py b/docs/examples/messages_and_events/beep_typed.py deleted file mode 100644 index 179965378..000000000 --- a/docs/examples/messages_and_events/beep_typed.py +++ /dev/null @@ -1,10 +0,0 @@ -from textual.app import App -from textual import events - - -class Beeper(App): - async def on_key(self, event: events.Key) -> None: - self.console.bell() - - -Beeper.run() diff --git a/docs/examples/messages_and_events/color_changer.py b/docs/examples/messages_and_events/color_changer.py deleted file mode 100644 index 58e19c9db..000000000 --- a/docs/examples/messages_and_events/color_changer.py +++ /dev/null @@ -1,10 +0,0 @@ -from textual.app import App - - -class ColorChanger(App): - def on_key(self, event): - if event.key.isdigit(): - self.background = f"on color({event.key})" - - -ColorChanger.run(log_path="textual.log") diff --git a/docs/examples/simple.py b/docs/examples/simple.py new file mode 100644 index 000000000..764afdba8 --- /dev/null +++ b/docs/examples/simple.py @@ -0,0 +1,28 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + + +class TextApp(App): + CSS = """ + Screen { + background: darkblue; + color: white; + layout: vertical; + } + Static { + height: auto; + padding: 2; + border: heavy white; + background: #ffffff 30%; + content-align: center middle; + /**/ + } + + """ + + def compose(self) -> ComposeResult: + yield Static("Hello") + yield Static("[b]World![/b]") + + +app = TextApp() diff --git a/docs/examples/timers/clock.py b/docs/examples/timers/clock.py deleted file mode 100644 index 53f412da8..000000000 --- a/docs/examples/timers/clock.py +++ /dev/null @@ -1,24 +0,0 @@ -from datetime import datetime - -from rich.align import Align -from rich.style import Style - -from textual.app import App -from textual.widget import Widget - - -class Clock(Widget): - def on_mount(self): - self.set_interval(1, self.refresh) - - def render(self, style: Style): - time = datetime.now().strftime("%c") - return Align.center(time, vertical="middle") - - -class ClockApp(App): - async def on_mount(self): - await self.screen.dock(Clock()) - - -ClockApp.run() diff --git a/docs/examples/widgets/custom.py b/docs/examples/widgets/custom.py deleted file mode 100644 index f35e82f41..000000000 --- a/docs/examples/widgets/custom.py +++ /dev/null @@ -1,33 +0,0 @@ -from rich.panel import Panel -from rich.style import Style - -from textual.app import App -from textual.reactive import Reactive -from textual.widget import Widget - - -class Hover(Widget): - - mouse_over = Reactive(False) - - def render(self, style: Style) -> Panel: - return Panel("Hello [b]World[/b]", style=("on red" if self.mouse_over else "")) - - def on_enter(self) -> None: - self.mouse_over = True - - def on_leave(self) -> None: - self.mouse_over = False - - -class HoverApp(App): - """Demonstrates smooth animation""" - - async def on_mount(self) -> None: - """Build layout here.""" - - hovers = (Hover() for _ in range(10)) - await self.screen.dock(*hovers, edge="top") - - -HoverApp.run(log_path="textual.log") diff --git a/docs/examples/widgets/placeholders.py b/docs/examples/widgets/placeholders.py deleted file mode 100644 index 9f7a96069..000000000 --- a/docs/examples/widgets/placeholders.py +++ /dev/null @@ -1,15 +0,0 @@ -from textual.app import App -from textual.widgets import Placeholder - - -class SimpleApp(App): - """Demonstrates smooth animation""" - - async def on_mount(self) -> None: - """Build layout here.""" - - await self.screen.dock(Placeholder(), edge="left", size=40) - await self.screen.dock(Placeholder(), Placeholder(), edge="top") - - -SimpleApp.run(log_path="textual.log") diff --git a/docs/index.md b/docs/index.md index 79d76e1ae..8e6772806 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,8 +4,22 @@ Textual is framework for rapidly creating _text user interfaces_ (TUIs from here A TUI is an application that lives within a terminal, which can have mouse and keyboard support and user interface elements like windows and panels, but is rendered purely with text. They have a number of advantages over GUI applications: they can be launched from the command line, and return to the command line, and they work over ssh. +## Foo + Creating a TUI can be challenging. It may be easier to create a GUI or web application than it is to build a TUI with traditional techniques. Often projects that could use one or the other never manage to ship either. -Textual seeks to lower the difficulty level of building a TUI by borrowing developments from the web world and to a lesser extent desktop applications. The goal is for it to be as easy to develop a TUI for your project as it would be to add a command line interface. +Textual seeks to lower the difficulty level of building a TUI by borrowing developments from the web world and to a lesser extent desktop applications. The goal is for it to be as easy to develop a TUI for your project as it would be to add a command line interface.XX + +=== "Python" + + ```python + --8<-- "docs/examples/simple.py" + ``` + +=== "Terminal" + + ```{.textual columns="40" lines="10"} + --8<-- "docs/examples/simple.py" + ``` Textual also offers a number of enhancements over traditional TUI applications by taking advantage of improvements to terminal software and the hardware it runs on. Terminals are a far cry from their roots in ancient hardware and dial-up modems, yet much of the software that runs on them hasn't kept pace. diff --git a/mkdocs.yml b/mkdocs.yml index a12a7cd98..af3107038 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,12 +1,26 @@ site_name: Textual -site_url: https://example.com/ +site_url: https://www.textualize.io/ markdown_extensions: - - pymdownx.highlight - - pymdownx.superfences + - admonition + - meta + - toc: + permalink: true + - pymdownx.keys + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences: + custom_fences: + - name: textual + class: textual + format: !!python/name:textual._doc.format_svg - pymdownx.inlinehilite - pymdownx.superfences - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - pymdownx.snippets + - markdown.extensions.attr_list theme: name: material diff --git a/poetry.lock b/poetry.lock index ad7a35df9..ee576a108 100644 --- a/poetry.lock +++ b/poetry.lock @@ -166,7 +166,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "6.3.3" +version = "6.4" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -219,7 +219,7 @@ dev = ["twine", "markdown", "flake8", "wheel"] [[package]] name = "identify" -version = "2.5.0" +version = "2.5.1" description = "File identification library for Python" category = "dev" optional = false @@ -238,7 +238,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.3" +version = "4.11.4" description = "Read metadata from Python packages" category = "main" optional = false @@ -263,11 +263,11 @@ python-versions = "*" [[package]] name = "jinja2" -version = "3.1.2" +version = "3.0.3" description = "A very fast and expressive template engine." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.dependencies] MarkupSafe = ">=2.0" @@ -650,7 +650,7 @@ pyyaml = "*" [[package]] name = "rich" -version = "12.4.1" +version = "12.4.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false @@ -701,7 +701,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.3" +version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -773,7 +773,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "3579be8d55deb729ef79984823765900552e19284c205b7bedc92897190fb6fd" +content-hash = "f84a265bad38b0894c5f13b1f1abeec5b5a9f4aab3aab44d90e761103a756743" [metadata.files] aiohttp = [ @@ -924,47 +924,47 @@ commonmark = [ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] coverage = [ - {file = "coverage-6.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df32ee0f4935a101e4b9a5f07b617d884a531ed5666671ff6ac66d2e8e8246d8"}, - {file = "coverage-6.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75b5dbffc334e0beb4f6c503fb95e6d422770fd2d1b40a64898ea26d6c02742d"}, - {file = "coverage-6.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114944e6061b68a801c5da5427b9173a0dd9d32cd5fcc18a13de90352843737d"}, - {file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab88a01cd180b5640ccc9c47232e31924d5f9967ab7edd7e5c91c68eee47a69"}, - {file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad8f9068f5972a46d50fe5f32c09d6ee11da69c560fcb1b4c3baea246ca4109b"}, - {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4cd696aa712e6cd16898d63cf66139dc70d998f8121ab558f0e1936396dbc579"}, - {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c1a9942e282cc9d3ed522cd3e3cab081149b27ea3bda72d6f61f84eaf88c1a63"}, - {file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c06455121a089252b5943ea682187a4e0a5cf0a3fb980eb8e7ce394b144430a9"}, - {file = "coverage-6.3.3-cp310-cp310-win32.whl", hash = "sha256:cb5311d6ccbd22578c80028c5e292a7ab9adb91bd62c1982087fad75abe2e63d"}, - {file = "coverage-6.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:6d4a6f30f611e657495cc81a07ff7aa8cd949144e7667c5d3e680d73ba7a70e4"}, - {file = "coverage-6.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79bf405432428e989cad7b8bc60581963238f7645ae8a404f5dce90236cc0293"}, - {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:338c417613f15596af9eb7a39353b60abec9d8ce1080aedba5ecee6a5d85f8d3"}, - {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db094a6a4ae6329ed322a8973f83630b12715654c197dd392410400a5bfa1a73"}, - {file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1414e8b124611bf4df8d77215bd32cba6e3425da8ce9c1f1046149615e3a9a31"}, - {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:93b16b08f94c92cab88073ffd185070cdcb29f1b98df8b28e6649145b7f2c90d"}, - {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbc86ae8cc129c801e7baaafe3addf3c8d49c9c1597c44bdf2d78139707c3c62"}, - {file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b5ba058610e8289a07db2a57bce45a1793ec0d3d11db28c047aae2aa1a832572"}, - {file = "coverage-6.3.3-cp37-cp37m-win32.whl", hash = "sha256:8329635c0781927a2c6ae068461e19674c564e05b86736ab8eb29c420ee7dc20"}, - {file = "coverage-6.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:e5af1feee71099ae2e3b086ec04f57f9950e1be9ecf6c420696fea7977b84738"}, - {file = "coverage-6.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e814a4a5a1d95223b08cdb0f4f57029e8eab22ffdbae2f97107aeef28554517e"}, - {file = "coverage-6.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f4fbf3633cb0713437291b8848634ea97f89c7e849c2be17a665611e433f53"}, - {file = "coverage-6.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3401b0d2ed9f726fadbfa35102e00d1b3547b73772a1de5508ef3bdbcb36afe7"}, - {file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8586b177b4407f988731eb7f41967415b2197f35e2a6ee1a9b9b561f6323c8e9"}, - {file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892e7fe32191960da559a14536768a62e83e87bbb867e1b9c643e7e0fbce2579"}, - {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afb03f981fadb5aed1ac6e3dd34f0488e1a0875623d557b6fad09b97a942b38a"}, - {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cbe91bc84be4e5ef0b1480d15c7b18e29c73bdfa33e07d3725da7d18e1b0aff2"}, - {file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:91502bf27cbd5c83c95cfea291ef387469f2387508645602e1ca0fd8a4ba7548"}, - {file = "coverage-6.3.3-cp38-cp38-win32.whl", hash = "sha256:c488db059848702aff30aa1d90ef87928d4e72e4f00717343800546fdbff0a94"}, - {file = "coverage-6.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6534fcdfb5c503affb6b1130db7b5bfc8a0f77fa34880146f7a5c117987d0"}, - {file = "coverage-6.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc692c9ee18f0dd3214843779ba6b275ee4bb9b9a5745ba64265bce911aefd1a"}, - {file = "coverage-6.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:462105283de203df8de58a68c1bb4ba2a8a164097c2379f664fa81d6baf94b81"}, - {file = "coverage-6.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc972d829ad5ef4d4c5fcabd2bbe2add84ce8236f64ba1c0c72185da3a273130"}, - {file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06f54765cdbce99901871d50fe9f41d58213f18e98b170a30ca34f47de7dd5e8"}, - {file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7835f76a081787f0ca62a53504361b3869840a1620049b56d803a8cb3a9eeea3"}, - {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f5fee77ec3384b934797f1873758f796dfb4f167e1296dc00f8b2e023ce6ee9"}, - {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:baa8be8aba3dd1e976e68677be68a960a633a6d44c325757aefaa4d66175050f"}, - {file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d06380e777dd6b35ee936f333d55b53dc4a8271036ff884c909cf6e94be8b6c"}, - {file = "coverage-6.3.3-cp39-cp39-win32.whl", hash = "sha256:f8cabc5fd0091976ab7b020f5708335033e422de25e20ddf9416bdce2b7e07d8"}, - {file = "coverage-6.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c9441d57b0963cf8340268ad62fc83de61f1613034b79c2b1053046af0c5284"}, - {file = "coverage-6.3.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:d522f1dc49127eab0bfbba4e90fa068ecff0899bbf61bf4065c790ddd6c177fe"}, - {file = "coverage-6.3.3.tar.gz", hash = "sha256:2781c43bffbbec2b8867376d4d61916f5e9c4cc168232528562a61d1b4b01879"}, + {file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"}, + {file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"}, + {file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"}, + {file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"}, + {file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"}, + {file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"}, + {file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"}, + {file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"}, + {file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"}, + {file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"}, + {file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"}, + {file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"}, + {file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"}, + {file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"}, + {file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"}, + {file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"}, + {file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, @@ -1040,24 +1040,24 @@ ghp-import = [ {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] identify = [ - {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"}, - {file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"}, + {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, + {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, - {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] markdown = [ {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, @@ -1360,8 +1360,8 @@ pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] rich = [ - {file = "rich-12.4.1-py3-none-any.whl", hash = "sha256:d13c6c90c42e24eb7ce660db397e8c398edd58acb7f92a2a88a95572b838aaa4"}, - {file = "rich-12.4.1.tar.gz", hash = "sha256:d239001c0fb7de985e21ec9a4bb542b5150350330bbc1849f835b9cbc8923b91"}, + {file = "rich-12.4.2-py3-none-any.whl", hash = "sha256:abcecd4444fa27cf84de412f713305e0d5481c941f12fc202fa4fc6711f80feb"}, + {file = "rich-12.4.2.tar.gz", hash = "sha256:cd8c809da089740f4bd94fa6d544cf23421d4bad34988569c2d03fdbdb858f0d"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1418,30 +1418,30 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"}, - {file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"}, - {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"}, - {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"}, - {file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"}, - {file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"}, - {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"}, - {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"}, - {file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"}, - {file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"}, - {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"}, - {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"}, - {file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"}, - {file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"}, - {file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"}, - {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"}, - {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"}, - {file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"}, - {file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"}, - {file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"}, - {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"}, - {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"}, - {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, - {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, diff --git a/pyproject.toml b/pyproject.toml index f0ac188a2..57089f7fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ textual = "textual.cli.cli:run" [tool.poetry.dependencies] python = "^3.7" -rich = "^12.4.0" +rich = "^12.4.2" #rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"} click = "8.1.2" @@ -35,12 +35,13 @@ pytest = "^6.2.3" black = "^22.3.0" mypy = "^0.950" pytest-cov = "^2.12.1" -mkdocs = "^1.2.3" +mkdocs = "^1.3.0" mkdocstrings = "^0.17.0" mkdocs-material = "^7.3.6" pre-commit = "^2.13.0" pytest-aiohttp = "^1.0.4" time-machine = "^2.6.0" +Jinja2 = "<3.1.0" [tool.black] includes = "src" diff --git a/sandbox/basic.py b/sandbox/basic.py index 63aed9561..8dfc6a162 100644 --- a/sandbox/basic.py +++ b/sandbox/basic.py @@ -158,11 +158,7 @@ class BasicApp(App): tweet_body.refresh(layout=True) -app = BasicApp( - css_path="basic.css", - watch_css=True, - log_path="textual.log", -) +app = BasicApp(css_path="basic.css") if __name__ == "__main__": app.run() diff --git a/sandbox/simplest.py b/sandbox/simplest.py new file mode 100644 index 000000000..1f1524485 --- /dev/null +++ b/sandbox/simplest.py @@ -0,0 +1,3 @@ +from textual.app import App + +app = App() diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index d36fc61c0..631292e98 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -552,7 +552,7 @@ class Compositor: ] return segment_lines - def render(self) -> RenderableType: + def render(self, full: bool = True) -> RenderableType: """Render a layout. Returns: @@ -561,11 +561,14 @@ class Compositor: width, height = self.size screen_region = Region(0, 0, width, height) - update_regions = self._dirty_regions.copy() - self._dirty_regions.clear() - if screen_region in update_regions: - # If one of the updates is the entire screen, then we only need one update - update_regions.clear() + if not full: + update_regions = self._dirty_regions.copy() + self._dirty_regions.clear() + if screen_region in update_regions: + # If one of the updates is the entire screen, then we only need one update + update_regions.clear() + else: + update_regions = set() if update_regions: # Create a crop regions that surrounds all updates diff --git a/src/textual/_doc.py b/src/textual/_doc.py new file mode 100644 index 000000000..755eab1b5 --- /dev/null +++ b/src/textual/_doc.py @@ -0,0 +1,14 @@ +import os + + +def format_svg(source, language, css_class, options, md, attrs, **kwargs): + os.environ["TEXTUAL"] = "headless" + os.environ["TEXTUAL_SCREENSHOT"] = "0.1" + os.environ["COLUMNS"] = attrs.get("columns", "80") + os.environ["LINES"] = attrs.get("lines", "24") + app_vars = {} + exec(source, app_vars) + app = app_vars["app"] + app.run() + svg = app._screenshot + return svg diff --git a/src/textual/app.py b/src/textual/app.py index 33a083cc1..31d91b8dc 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -136,8 +136,13 @@ class App(Generic[ReturnType], DOMNode): # this will create some first references to an asyncio loop. _init_uvloop() + self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", "")) + self.console = Console( - file=sys.__stdout__, markup=False, highlight=False, emoji=False + file=(open(os.devnull, "wt") if self.is_headless else sys.__stdout__), + markup=True, + highlight=False, + emoji=False, ) self.error_console = Console(markup=False, stderr=True) self.driver_class = driver_class or self.get_driver_class() @@ -186,8 +191,6 @@ class App(Generic[ReturnType], DOMNode): self.css_path = css_path - self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", "")) - self.registry: set[MessagePump] = set() self.devtools = DevtoolsClient() self._return_value: ReturnType | None = None @@ -215,6 +218,11 @@ class App(Generic[ReturnType], DOMNode): """Check if debug mode is enabled.""" return "debug" in self.features + @property + def is_headless(self) -> bool: + """Check if the app is running in 'headless' mode.""" + return "headless" in self.features + def exit(self, result: ReturnType | None = None) -> None: """Exit the app, and return the supplied result. @@ -430,7 +438,7 @@ class App(Generic[ReturnType], DOMNode): color_system="truecolor", record=True, ) - console.print(self.screen._compositor) + console.print(self.screen._compositor.render(full=True)) return console.export_svg(title=self.title) def save_screenshot(self, path: str | None = None) -> str: @@ -443,6 +451,7 @@ class App(Generic[ReturnType], DOMNode): Returns: str: Filename of screenshot. """ + self.bell() if path is None: svg_path = f"{self.title.lower()}_{datetime.now().isoformat()}.svg" svg_path = svg_path.replace("/", "_").replace("\\", "_") @@ -727,13 +736,12 @@ class App(Generic[ReturnType], DOMNode): mount_event = events.Mount(sender=self) await self.dispatch_message(mount_event) - # TODO: don't override `self.console` here - self.console = Console(file=sys.__stdout__) self.title = self._title self.refresh() await self.animator.start() with redirect_stdout(StdoutRedirector(self.devtools, self._log_file)): # type: ignore + await self._ready() await super().process_messages() await self.animator.stop() await self.close_all() @@ -754,6 +762,22 @@ class App(Generic[ReturnType], DOMNode): self._log_file.close() self._log_console = None + async def _ready(self) -> None: + try: + screenshot_timer = float(os.environ.get("TEXTUAL_SCREENSHOT", "0")) + except ValueError: + return + + if not screenshot_timer: + return + + async def on_screenshot(): + svg = self.export_screenshot() + self._screenshot = svg + await self.shutdown() + + self.set_timer(screenshot_timer, on_screenshot) + def on_mount(self) -> None: widgets = list(self.compose()) if widgets: diff --git a/src/textual/cli/cli.py b/src/textual/cli/cli.py index b6b882718..34b5c0adc 100644 --- a/src/textual/cli/cli.py +++ b/src/textual/cli/cli.py @@ -1,8 +1,16 @@ -import click +from __future__ import annotations + +from typing import TYPE_CHECKING + from importlib_metadata import version +import click + from textual.devtools.server import _run_devtools +if TYPE_CHECKING: + from textual.app import App + @click.group() @click.version_option(version("textual")) @@ -13,3 +21,66 @@ def run(): @run.command(help="Run the Textual Devtools console") def console(): _run_devtools() + + +class AppFail(Exception): + pass + + +def import_app(import_name: str) -> App: + import inspect + import importlib + import sys + + from textual.app import App + + sys.path.append("") + + lib, _colon, name = import_name.partition(":") + name = name or "app" + + try: + module = importlib.import_module(lib) + except ImportError as error: + raise + raise AppFail(str(error)) + + try: + app = getattr(module, name or "app") + except AttributeError: + raise AppFail(f"Unable to find {name!r} in {module!r}") + + if inspect.isclass(app) and issubclass(app, App): + app = App() + + return app + + +@run.command("run") +@click.argument("import_name", metavar="IMPORT") +@click.option("--dev", "dev", help="Enable development mode", is_flag=True) +def run_app(import_name: str, dev: bool) -> None: + """Run a Textual app.""" + + import os + import sys + + from textual.features import parse_features + + features = set(parse_features(os.environ.get("TEXTUAL", ""))) + if dev: + features.add("debug") + features.add("dev") + + os.environ["TEXTUAL"] = ",".join(sorted(features)) + + try: + app = import_app(import_name) + except AppFail as error: + from rich.console import Console + + console = Console(stderr=True) + console.print(str(error)) + sys.exit(1) + + app.run() diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 5e0d97175..46eeb1162 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -14,8 +14,9 @@ from threading import Event, Thread if TYPE_CHECKING: from rich.console import Console -from .. import log +import rich.repr +from .. import log from .. import events from ..driver import Driver from ..geometry import Size @@ -24,6 +25,7 @@ from .._xterm_parser import XTermParser from .._profile import timer +@rich.repr.auto class LinuxDriver(Driver): """Powers display and input for Linux / MacOS""" @@ -36,14 +38,19 @@ class LinuxDriver(Driver): self.exit_event = Event() self._key_thread: Thread | None = None + def __rich_repr__(self) -> rich.repr.Result: + yield "debug", self._debug + def _get_terminal_size(self) -> tuple[int, int]: width: int | None = 80 height: int | None = 25 + import shutil + try: - width, height = os.get_terminal_size(sys.__stdin__.fileno()) + width, height = shutil.get_terminal_size() except (AttributeError, ValueError, OSError): try: - width, height = os.get_terminal_size(sys.__stdout__.fileno()) + width, height = shutil.get_terminal_size() except (AttributeError, ValueError, OSError): pass width = width or 80 diff --git a/src/textual/features.py b/src/textual/features.py index 0868cd904..bc9b984db 100644 --- a/src/textual/features.py +++ b/src/textual/features.py @@ -9,16 +9,16 @@ if sys.version_info >= (3, 8): else: from typing_extensions import Final, Literal -FEATURES: Final = {"devtools", "debug"} +FEATURES: Final = {"devtools", "debug", "headless"} -FeatureFlag = Literal["devtools", "debug"] +FeatureFlag = Literal["devtools", "debug", "headless"] def parse_features(features: str) -> frozenset[FeatureFlag]: """Parse features env var Args: - features (str): Comma seprated feature flags + features (str): Comma separated feature flags Returns: frozenset[FeatureFlag]: A frozen set of known features.