Skip to content

Commit e0f212c

Browse files
authored
Merge pull request #5719 from Textualize/notification-markup
Use Textual markup for notifications
2 parents f21073f + 7e7a662 commit e0f212c

File tree

9 files changed

+217
-26
lines changed

9 files changed

+217
-26
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1717
- Collapsible title now accepts str, Text, or Content https://github.com/Textualize/textual/pull/5697
1818
- Rich Text objects will be converted to Content in OptionList and other widgets https://github.com/Textualize/textual/pull/5712
1919
- Textual will always convert dim attributes to RGB by default https://github.com/Textualize/textual/pull/5715
20+
- Notifications will now use Textual markup (previously they used Console markup) https://github.com/Textualize/textual/pull/5719
2021

2122
### Added
2223

2324
- Added `TEXTUAL_DIM_FACTOR` env var to set the opacity of the 'dim' ANSI attribute https://github.com/Textualize/textual/pull/5715
25+
- `notify()` now accepts a `markup` parameter to disable rendering the message as markup https://github.com/Textualize/textual/pull/5719
2426

2527
## [3.0.1] - 2025-04-01
2628

@@ -35,7 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3537
- Breaking change: `App.query` and friends will now always query the default (first) screen, not necessarily the active screen.
3638
- Content now has a default argument of an empty string, so `Content()` is equivalent to `Content("")`
3739
- Assigned names to Textual-specific threads: `textual-input`, `textual-output`. These should become visible in monitoring tools (ps, top, htop) as of Python 3.14. https://github.com/Textualize/textual/pull/5654
38-
- Tabs now accept Content or content markup https://github.com/Textualize/textual/pull/5657
40+
- Tabs now accept Content or Textual markup https://github.com/Textualize/textual/pull/5657
3941
- Buttons will now use Textual markup rather than console markup
4042
- tree-sitter languages are now loaded lazily, improving cold-start time https://github.com/Textualize/textual/pull/563
4143

@@ -122,7 +124,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
122124

123125
### Fixed
124126

125-
- Fixed escape tags in Content markup https://github.com/Textualize/textual/pull/5536
127+
- Fixed escape tags in Textual markup https://github.com/Textualize/textual/pull/5536
126128

127129
## [2.0.0] - 2025-02-16
128130

src/textual/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4285,6 +4285,7 @@ def notify(
42854285
title: str = "",
42864286
severity: SeverityLevel = "information",
42874287
timeout: float | None = None,
4288+
markup: bool = True,
42884289
) -> None:
42894290
"""Create a notification.
42904291
@@ -4298,6 +4299,7 @@ def notify(
42984299
title: The title for the notification.
42994300
severity: The severity of the notification.
43004301
timeout: The timeout (in seconds) for the notification, or `None` for default.
4302+
markup: Render the message as Textual markup?
43014303
43024304
The `notify` method is used to create an application-wide
43034305
notification, shown in a [`Toast`][textual.widgets._toast.Toast],
@@ -4334,7 +4336,7 @@ def notify(
43344336
"""
43354337
if timeout is None:
43364338
timeout = self.NOTIFICATION_TIMEOUT
4337-
notification = Notification(message, title, severity, timeout)
4339+
notification = Notification(message, title, severity, timeout, markup=markup)
43384340
self.post_message(Notify(notification))
43394341

43404342
def _on_notify(self, event: Notify) -> None:

src/textual/content.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def __str__(self) -> str:
145145

146146
@cached_property
147147
def markup(self) -> str:
148-
"""Get Content markup to render this Text.
148+
"""Get Textual markup to render this Text.
149149
150150
Returns:
151151
str: A string potentially creating markup tags.

src/textual/layout.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,11 @@ def get_content_height(
262262
):
263263
# An exception for containers with all dynamic height widgets
264264
arrangement = widget._arrange(Size(width, container.height))
265-
return arrangement.total_region.bottom
266265
else:
267266
arrangement = widget._arrange(Size(width, 0))
268-
height = arrangement.total_region.bottom
267+
height = arrangement.total_region.height
269268
else:
270269
height = 0
271-
272270
return height
273271

274272
def render_keyline(self, container: Widget) -> StripRenderable:

src/textual/notifications.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class Notification:
3939
timeout: float = 5
4040
"""The timeout (in seconds) for the notification."""
4141

42+
markup: bool = False
43+
"""Render the notification message as Textual Markup?"""
44+
4245
raised_at: float = field(default_factory=time)
4346
"""The time when the notification was raised (in Unix time)."""
4447

src/textual/widget.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4564,6 +4564,7 @@ def notify(
45644564
title: str = "",
45654565
severity: SeverityLevel = "information",
45664566
timeout: float | None = None,
4567+
markup: bool = True,
45674568
) -> None:
45684569
"""Create a notification.
45694570
@@ -4576,6 +4577,7 @@ def notify(
45764577
title: The title for the notification.
45774578
severity: The severity of the notification.
45784579
timeout: The timeout (in seconds) for the notification, or `None` for default.
4580+
markup: Render the message as Textual markup?
45794581
45804582
See [`App.notify`][textual.app.App.notify] for the full
45814583
documentation for this method.
@@ -4585,16 +4587,27 @@ def notify(
45854587
message,
45864588
title=title,
45874589
severity=severity,
4590+
markup=markup,
45884591
)
45894592
else:
45904593
return self.app.notify(
45914594
message,
45924595
title=title,
45934596
severity=severity,
45944597
timeout=timeout,
4598+
markup=markup,
45954599
)
45964600

45974601
def action_notify(
4598-
self, message: str, title: str = "", severity: str = "information"
4602+
self,
4603+
message: str,
4604+
title: str = "",
4605+
severity: str = "information",
4606+
markup: bool = True,
45994607
) -> None:
4600-
self.notify(message, title=title, severity=severity)
4608+
self.notify(
4609+
message,
4610+
title=title,
4611+
severity=severity,
4612+
markup=markup,
4613+
)

src/textual/widgets/_toast.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
from typing import TYPE_CHECKING, ClassVar
66

7-
from rich.text import Text
8-
97
from textual import on
108

119
if TYPE_CHECKING:
12-
from textual.app import RenderResult
10+
pass
1311

1412
from textual.containers import Container
13+
from textual.content import Content
1514
from textual.css.query import NoMatches
1615
from textual.events import Click, Mount
1716
from textual.notifications import Notification, Notifications
@@ -30,7 +29,7 @@ class ToastHolder(Container, inherit_css=False):
3029
align-horizontal: right;
3130
width: 1fr;
3231
height: auto;
33-
visibility: hidden;
32+
visibility: hidden;
3433
}
3534
"""
3635

@@ -43,8 +42,8 @@ class Toast(Static, inherit_css=False):
4342
width: 60;
4443
max-width: 50%;
4544
height: auto;
46-
visibility: visible;
4745
margin-top: 1;
46+
visibility: visible;
4847
padding: 1 1;
4948
background: $panel-lighten-1;
5049
link-background: initial;
@@ -104,25 +103,27 @@ def __init__(self, notification: Notification) -> None:
104103
self._notification = notification
105104
self._timeout = notification.time_left
106105

107-
def render(self) -> RenderResult:
106+
def render(self) -> Content:
108107
"""Render the toast's content.
109108
110109
Returns:
111110
A Rich renderable for the title and content of the Toast.
112111
"""
113112
notification = self._notification
113+
114+
message_content = (
115+
Content.from_markup(notification.message)
116+
if notification.markup
117+
else Content(notification.message)
118+
)
119+
114120
if notification.title:
115-
header_style = self.get_component_rich_style("toast--title")
116-
notification_text = Text.assemble(
117-
(notification.title, header_style),
118-
"\n",
119-
Text.from_markup(notification.message),
120-
)
121-
else:
122-
notification_text = Text.assemble(
123-
Text.from_markup(notification.message),
121+
header_style = self.get_visual_style("toast--title")
122+
message_content = Content.assemble(
123+
(notification.title, header_style), "\n", message_content
124124
)
125-
return notification_text
125+
126+
return message_content
126127

127128
def _on_mount(self, _: Mount) -> None:
128129
"""Set the time running once the toast is mounted."""
@@ -155,7 +156,7 @@ class ToastRack(Container, inherit_css=False):
155156
visibility: hidden;
156157
layout: vertical;
157158
overflow-y: scroll;
158-
margin-bottom: 1;
159+
margin-bottom: 1;
159160
}
160161
"""
161162
DEFAULT_CLASSES = "-textual-system"

0 commit comments

Comments
 (0)