mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1333 from Textualize/textlog-docs
textlog documentation
This commit is contained in:
1
docs/api/text_log.md
Normal file
1
docs/api/text_log.md
Normal file
@@ -0,0 +1 @@
|
||||
::: textual.widgets.TextLog
|
||||
66
docs/examples/widgets/text_log.py
Normal file
66
docs/examples/widgets/text_log.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
from rich.table import Table
|
||||
from rich.syntax import Syntax
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual import events
|
||||
from textual.widgets import TextLog
|
||||
|
||||
|
||||
CSV = """lane,swimmer,country,time
|
||||
4,Joseph Schooling,Singapore,50.39
|
||||
2,Michael Phelps,United States,51.14
|
||||
5,Chad le Clos,South Africa,51.14
|
||||
6,László Cseh,Hungary,51.14
|
||||
3,Li Zhuhao,China,51.26
|
||||
8,Mehdy Metella,France,51.58
|
||||
7,Tom Shields,United States,51.73
|
||||
1,Aleksandr Sadovnikov,Russia,51.84"""
|
||||
|
||||
|
||||
CODE = '''\
|
||||
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for first and last value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
previous_value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
first = True
|
||||
for value in iter_values:
|
||||
yield first, False, previous_value
|
||||
first = False
|
||||
previous_value = value
|
||||
yield first, True, previous_value\
|
||||
'''
|
||||
|
||||
|
||||
class TextLogApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield TextLog(highlight=True, markup=True)
|
||||
|
||||
def on_ready(self) -> None:
|
||||
"""Called when the DOM is ready."""
|
||||
text_log = self.query_one(TextLog)
|
||||
|
||||
text_log.write(Syntax(CODE, "python", indent_guides=True))
|
||||
|
||||
rows = iter(csv.reader(io.StringIO(CSV)))
|
||||
table = Table(*next(rows))
|
||||
for row in rows:
|
||||
table.add_row(*row)
|
||||
|
||||
text_log.write(table)
|
||||
text_log.write("[bold magenta]Write text or any Rich renderable!")
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
"""Write Key events to log."""
|
||||
text_log = self.query_one(TextLog)
|
||||
text_log.write(event)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = TextLogApp()
|
||||
app.run()
|
||||
44
docs/widgets/text_log.md
Normal file
44
docs/widgets/text_log.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# TextLog
|
||||
|
||||
A TextLog is a widget which displays scrollable content that may be appended to in realtime.
|
||||
|
||||
Call [TextLog.write][textual.widgets.TextLog.write] with a string or [Rich Renderable](https://rich.readthedocs.io/en/latest/protocol.html) to write content to the end of the TextLog. Call [TextLog.clear][textual.widgets.TextLog.clear] to clear the content.
|
||||
|
||||
- [X] Focusable
|
||||
- [ ] Container
|
||||
|
||||
## Example
|
||||
|
||||
The example below shows each placeholder variant.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/text_log.py" press="_,H,i"}
|
||||
```
|
||||
|
||||
=== "text_log.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/text_log.py"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----------- | ------ | ------- | ------------------------------------------------------------ |
|
||||
| `highlight` | `bool` | `False` | Automatically highlight content. |
|
||||
| `markup` | `bool` | `False` | Apply Rich console markup. |
|
||||
| `max_lines` | `int` | `None` | Maximum number of lines in the log or `None` for no maximum. |
|
||||
| `min_width` | `int` | 78 | Minimum width of renderables. |
|
||||
| `wrap` | `bool` | `False` | Enable word wrapping. |
|
||||
|
||||
## Messages
|
||||
|
||||
This widget sends no messages.
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
* [TextLog](../api/textlog.md) code reference
|
||||
@@ -92,6 +92,7 @@ nav:
|
||||
- "widgets/button.md"
|
||||
- "widgets/checkbox.md"
|
||||
- "widgets/data_table.md"
|
||||
- "widgets/text_log.md"
|
||||
- "widgets/directory_tree.md"
|
||||
- "widgets/footer.md"
|
||||
- "widgets/header.md"
|
||||
@@ -109,6 +110,7 @@ nav:
|
||||
- "api/color.md"
|
||||
- "api/containers.md"
|
||||
- "api/data_table.md"
|
||||
- "api/text_log.md"
|
||||
- "api/directory_tree.md"
|
||||
- "api/dom_node.md"
|
||||
- "api/events.md"
|
||||
|
||||
@@ -356,6 +356,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
)
|
||||
self._screenshot: str | None = None
|
||||
self._dom_lock = asyncio.Lock()
|
||||
self._dom_ready = False
|
||||
|
||||
@property
|
||||
def return_value(self) -> ReturnType | None:
|
||||
|
||||
@@ -140,6 +140,10 @@ class Hide(Event, bubble=False):
|
||||
"""
|
||||
|
||||
|
||||
class Ready(Event, bubble=False):
|
||||
"""Sent to the app when the DOM is ready."""
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class MouseCapture(Event, bubble=False):
|
||||
"""Sent when the mouse has been captured.
|
||||
|
||||
@@ -379,8 +379,6 @@ class Screen(Widget):
|
||||
|
||||
for widget in hidden:
|
||||
widget.post_message_no_wait(Hide(self))
|
||||
for widget in shown:
|
||||
widget.post_message_no_wait(Show(self))
|
||||
|
||||
# We want to send a resize event to widgets that were just added or change since last layout
|
||||
send_resize = shown | resized
|
||||
@@ -401,11 +399,17 @@ class Screen(Widget):
|
||||
ResizeEvent(self, region.size, virtual_size, container_size)
|
||||
)
|
||||
|
||||
for widget in shown:
|
||||
widget.post_message_no_wait(Show(self))
|
||||
|
||||
except Exception as error:
|
||||
self.app._handle_exception(error)
|
||||
return
|
||||
display_update = self._compositor.render(full=full)
|
||||
self.app._display(self, display_update)
|
||||
if not self.app._dom_ready:
|
||||
self.app.post_message_no_wait(events.Ready(self))
|
||||
self.app._dom_ready = True
|
||||
|
||||
async def _on_update(self, message: messages.Update) -> None:
|
||||
message.stop()
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import cast
|
||||
|
||||
from rich.console import RenderableType
|
||||
from rich.highlighter import ReprHighlighter
|
||||
from rich.measure import measure_renderables
|
||||
from rich.pretty import Pretty
|
||||
from rich.protocol import is_renderable
|
||||
from rich.segment import Segment
|
||||
@@ -73,22 +74,31 @@ class TextLog(ScrollView, can_focus=True):
|
||||
else:
|
||||
if isinstance(content, str):
|
||||
if self.markup:
|
||||
content = Text.from_markup(content)
|
||||
if self.highlight:
|
||||
renderable = self.highlighter(content)
|
||||
renderable = Text.from_markup(content)
|
||||
else:
|
||||
renderable = Text(content)
|
||||
if self.highlight:
|
||||
renderable = self.highlighter(content)
|
||||
else:
|
||||
renderable = cast(RenderableType, content)
|
||||
|
||||
console = self.app.console
|
||||
width = max(self.min_width, self.size.width or self.min_width)
|
||||
render_options = console.options
|
||||
|
||||
render_options = console.options.update_width(width)
|
||||
if not self.wrap:
|
||||
if isinstance(renderable, Text) and not self.wrap:
|
||||
render_options = render_options.update(overflow="ignore", no_wrap=True)
|
||||
segments = self.app.console.render(renderable, render_options.update_width(80))
|
||||
|
||||
width = max(
|
||||
self.min_width,
|
||||
measure_renderables(console, render_options, [renderable]).maximum,
|
||||
)
|
||||
|
||||
segments = self.app.console.render(
|
||||
renderable, render_options.update_width(width)
|
||||
)
|
||||
lines = list(Segment.split_lines(segments))
|
||||
if not lines:
|
||||
return
|
||||
|
||||
self.max_width = max(
|
||||
self.max_width,
|
||||
|
||||
Reference in New Issue
Block a user