Skip to content

Commit 879c985

Browse files
authored
Rich log (#3046)
* log * tests * snapshot tests * change to richlog * keep raw lines * disable highlighting by default * simplify * superfluous test * optimization * update cell length * add refresh * write method * version bump * doc fix link * makes lines private * docstring * relax dev dependancy * remove superfluous code [skip ci] * added FAQ [skipci] * fix code in faq [skipci] * fix typo * max lines fix
1 parent b045306 commit 879c985

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3361
-2346
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [0.32.0] - 2023-08-03
9+
10+
### Added
11+
12+
- Added widgets.Log
13+
- Added Widget.is_vertical_scroll_end, Widget.is_horizontal_scroll_end, Widget.is_vertical_scrollbar_grabbed, Widget.is_horizontal_scrollbar_grabbed
14+
15+
### Changed
16+
17+
- Breaking change: Renamed TextLog to RichLog
18+
819
## [0.31.0] - 2023-08-01
920

1021
### Added

FAQ.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
- [How can I select and copy text in a Textual app?](#how-can-i-select-and-copy-text-in-a-textual-app)
88
- [How can I set a translucent app background?](#how-can-i-set-a-translucent-app-background)
99
- [How do I center a widget in a screen?](#how-do-i-center-a-widget-in-a-screen)
10-
- [How do I fixed WorkerDeclarationError?](#how-do-i-fixed-workerdeclarationerror)
10+
- [How do I fix WorkerDeclarationError?](#how-do-i-fix-workerdeclarationerror)
1111
- [How do I pass arguments to an app?](#how-do-i-pass-arguments-to-an-app)
12+
- [No widget called TextLog](#no-widget-called-textlog)
1213
- [Why do some key combinations never make it to my app?](#why-do-some-key-combinations-never-make-it-to-my-app)
1314
- [Why doesn't Textual look good on macOS?](#why-doesn't-textual-look-good-on-macos)
1415
- [Why doesn't Textual support ANSI themes?](#why-doesn't-textual-support-ansi-themes)
@@ -145,8 +146,8 @@ if __name__ == "__main__":
145146
ButtonApp().run()
146147
```
147148

148-
<a name="how-do-i-fixed-workerdeclarationerror"></a>
149-
## How do I fixed WorkerDeclarationError?
149+
<a name="how-do-i-fix-workerdeclarationerror"></a>
150+
## How do I fix WorkerDeclarationError?
150151

151152
Textual version 0.31.0 requires that you set `thread=True` on the `@work` decorator if you want to run a threaded worker.
152153

@@ -158,15 +159,15 @@ def run_in_background():
158159
...
159160
```
160161

161-
If you *don't* want a worker, you should make your work function `async`:
162+
If you *don't* want a threaded worker, you should make your work function `async`:
162163

163164
```python
164-
@work():
165+
@work()
165166
async def run_in_background():
166167
...
167168
```
168169

169-
This change was made because it was easy to accidentally created a threaded worker, which may produce unexpected results.
170+
This change was made because it was too easy to accidentally create a threaded worker, which may produce unexpected results.
170171

171172
<a name="how-do-i-pass-arguments-to-an-app"></a>
172173
## How do I pass arguments to an app?
@@ -195,13 +196,26 @@ Then the app can be run, passing in various arguments; for example:
195196
# Running with default arguments.
196197
Greetings().run()
197198

198-
# Running with a keyword arguyment.
199+
# Running with a keyword argument.
199200
Greetings(to_greet="davep").run()
200201

201202
# Running with both positional arguments.
202203
Greetings("Well hello", "there").run()
203204
```
204205

206+
<a name="no-widget-called-textlog"></a>
207+
## No widget called TextLog
208+
209+
The `TextLog` widget was renamed to `RichLog` in Textual 0.32.0.
210+
You will need to replace all references to `TextLog` in your code, with `RichLog`.
211+
Most IDEs will have a search and replace function which will help you do this.
212+
213+
Here's how you should import RichLog:
214+
215+
```python
216+
from textual.widgets import RichLog
217+
```
218+
205219
<a name="why-do-some-key-combinations-never-make-it-to-my-app"></a>
206220
## Why do some key combinations never make it to my app?
207221

docs/examples/guide/input/key01.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
from textual.app import App, ComposeResult
2-
from textual.widgets import TextLog
31
from textual import events
2+
from textual.app import App, ComposeResult
3+
from textual.widgets import RichLog
44

55

66
class InputApp(App):
77
"""App to display key events."""
88

99
def compose(self) -> ComposeResult:
10-
yield TextLog()
10+
yield RichLog()
1111

1212
def on_key(self, event: events.Key) -> None:
13-
self.query_one(TextLog).write(event)
13+
self.query_one(RichLog).write(event)
1414

1515

1616
if __name__ == "__main__":

docs/examples/guide/input/key02.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
from textual.app import App, ComposeResult
2-
from textual.widgets import TextLog
31
from textual import events
2+
from textual.app import App, ComposeResult
3+
from textual.widgets import RichLog
44

55

66
class InputApp(App):
77
"""App to display key events."""
88

99
def compose(self) -> ComposeResult:
10-
yield TextLog()
10+
yield RichLog()
1111

1212
def on_key(self, event: events.Key) -> None:
13-
self.query_one(TextLog).write(event)
13+
self.query_one(RichLog).write(event)
1414

1515
def key_space(self) -> None:
1616
self.bell()

docs/examples/guide/input/key03.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from textual.app import App, ComposeResult
2-
from textual.widgets import TextLog
31
from textual import events
2+
from textual.app import App, ComposeResult
3+
from textual.widgets import RichLog
44

55

6-
class KeyLogger(TextLog):
6+
class KeyLogger(RichLog):
77
def on_key(self, event: events.Key) -> None:
88
self.write(event)
99

docs/examples/guide/input/mouse01.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from textual import events
22
from textual.app import App, ComposeResult
33
from textual.containers import Container
4-
from textual.widgets import Static, TextLog
4+
from textual.widgets import RichLog, Static
55

66

77
class PlayArea(Container):
88
def on_mount(self) -> None:
99
self.capture_mouse()
1010

1111
def on_mouse_move(self, event: events.MouseMove) -> None:
12-
self.screen.query_one(TextLog).write(event)
12+
self.screen.query_one(RichLog).write(event)
1313
self.query_one(Ball).offset = event.offset - (8, 2)
1414

1515

@@ -21,7 +21,7 @@ class MouseApp(App):
2121
CSS_PATH = "mouse01.css"
2222

2323
def compose(self) -> ComposeResult:
24-
yield TextLog()
24+
yield RichLog()
2525
yield PlayArea(Ball("Textual"))
2626

2727

docs/examples/guide/reactivity/validate01.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from textual.app import App, ComposeResult
22
from textual.containers import Horizontal
33
from textual.reactive import reactive
4-
from textual.widgets import Button, TextLog
4+
from textual.widgets import Button, RichLog
55

66

77
class ValidateApp(App):
@@ -23,14 +23,14 @@ def compose(self) -> ComposeResult:
2323
Button("-1", id="minus", variant="error"),
2424
id="buttons",
2525
)
26-
yield TextLog(highlight=True)
26+
yield RichLog(highlight=True)
2727

2828
def on_button_pressed(self, event: Button.Pressed) -> None:
2929
if event.button.id == "plus":
3030
self.count += 1
3131
else:
3232
self.count -= 1
33-
self.query_one(TextLog).write(f"{self.count=}")
33+
self.query_one(RichLog).write(f"{self.count=}")
3434

3535

3636
if __name__ == "__main__":

docs/examples/widgets/log.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from textual.app import App, ComposeResult
2+
from textual.widgets import Log
3+
4+
TEXT = """I must not fear.
5+
Fear is the mind-killer.
6+
Fear is the little-death that brings total obliteration.
7+
I will face my fear.
8+
I will permit it to pass over me and through me.
9+
And when it has gone past, I will turn the inner eye to see its path.
10+
Where the fear has gone there will be nothing. Only I will remain."""
11+
12+
13+
class LogApp(App):
14+
"""An app with a simple log."""
15+
16+
def compose(self) -> ComposeResult:
17+
yield Log()
18+
19+
def on_ready(self) -> None:
20+
log = self.query_one(Log)
21+
log.write_line("Hello, World!")
22+
for _ in range(10):
23+
log.write_line(TEXT)
24+
25+
26+
if __name__ == "__main__":
27+
app = LogApp()
28+
app.run()

docs/examples/widgets/text_log.py renamed to docs/examples/widgets/rich_log.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import csv
22
import io
33

4-
from rich.table import Table
54
from rich.syntax import Syntax
5+
from rich.table import Table
66

7-
from textual.app import App, ComposeResult
87
from textual import events
9-
from textual.widgets import TextLog
10-
8+
from textual.app import App, ComposeResult
9+
from textual.widgets import RichLog
1110

1211
CSV = """lane,swimmer,country,time
1312
4,Joseph Schooling,Singapore,50.39
@@ -37,13 +36,13 @@ def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
3736
'''
3837

3938

40-
class TextLogApp(App):
39+
class RichLogApp(App):
4140
def compose(self) -> ComposeResult:
42-
yield TextLog(highlight=True, markup=True)
41+
yield RichLog(highlight=True, markup=True)
4342

4443
def on_ready(self) -> None:
4544
"""Called when the DOM is ready."""
46-
text_log = self.query_one(TextLog)
45+
text_log = self.query_one(RichLog)
4746

4847
text_log.write(Syntax(CODE, "python", indent_guides=True))
4948

@@ -57,10 +56,10 @@ def on_ready(self) -> None:
5756

5857
def on_key(self, event: events.Key) -> None:
5958
"""Write Key events to log."""
60-
text_log = self.query_one(TextLog)
59+
text_log = self.query_one(RichLog)
6160
text_log.write(event)
6261

6362

6463
if __name__ == "__main__":
65-
app = TextLogApp()
64+
app = RichLogApp()
6665
app.run()

docs/guide/input.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The most fundamental way to receive input is via [Key][textual.events.Key] event
2323
```{.textual path="docs/examples/guide/input/key01.py", press="T,e,x,t,u,a,l,!"}
2424
```
2525

26-
When you press a key, the app will receive the event and write it to a [TextLog](../widgets/text_log.md) widget. Try pressing a few keys to see what happens.
26+
When you press a key, the app will receive the event and write it to a [RichLog](../widgets/rich_log.md) widget. Try pressing a few keys to see what happens.
2727

2828
!!! tip
2929

@@ -105,7 +105,7 @@ The following example shows how focus works in practice.
105105
```{.textual path="docs/examples/guide/input/key03.py", press="H,e,l,l,o,tab,W,o,r,l,d,!"}
106106
```
107107

108-
The app splits the screen in to quarters, with a `TextLog` widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that the widget has focus. Key events will be sent to the focused widget only.
108+
The app splits the screen in to quarters, with a `RichLog` widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that the widget has focus. Key events will be sent to the focused widget only.
109109

110110
!!! tip
111111

0 commit comments

Comments
 (0)