Skip to content

Commit 2d32ff3

Browse files
authored
Merge pull request #5950 from Textualize/markdown-stream
WIP Markdown streaming
2 parents 2c79009 + 03b9470 commit 2d32ff3

File tree

11 files changed

+422
-165
lines changed

11 files changed

+422
-165
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88

9-
## [3.7.2] - Unreleased
9+
## Unreleased
1010

1111
### Fixed
1212

1313
- Fixed `query_one` and `query_exactly_one` not raising documented `WrongType` exception.
1414

15+
### Changed
16+
17+
- Breaking change: `Widget.anchor` now has different semantics. It should be applied to a container and anchors to the bottom of the scroll position. https://github.com/Textualize/textual/pull/5950
18+
19+
### Added
20+
21+
- Added `Markdown.append` https://github.com/Textualize/textual/pull/5950
22+
- Added `Widget.release_anchor` https://github.com/Textualize/textual/pull/5950
23+
1524
## [3.7.1] - 2025-07-09
1625

1726
### Fixed

examples/mother.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def compose(self) -> ComposeResult:
7474
def on_mount(self) -> None:
7575
"""You might want to change the model if you don't have access to it."""
7676
self.model = llm.get_model("gpt-4o")
77+
self.query_one("#chat-view").anchor()
7778

7879
@on(Input.Submitted)
7980
async def on_input(self, event: Input.Submitted) -> None:
@@ -82,8 +83,6 @@ async def on_input(self, event: Input.Submitted) -> None:
8283
event.input.clear()
8384
await chat_view.mount(Prompt(event.value))
8485
await chat_view.mount(response := Response())
85-
response.anchor()
86-
8786
self.send_prompt(event.value, response)
8887

8988
@work(thread=True)

src/textual/_compositor.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
3737
from textual.map_geometry import MapGeometry
3838
from textual.strip import Strip, StripRenderable
39+
from textual.widget import Widget
3940

4041
if TYPE_CHECKING:
4142
from typing_extensions import TypeAlias
4243

4344
from textual.screen import Screen
44-
from textual.widget import Widget
4545

4646

4747
class ReflowResult(NamedTuple):
@@ -605,6 +605,18 @@ def add_widget(
605605
# Get the region that will be updated
606606
sub_clip = clip.intersection(child_region)
607607

608+
if widget._anchored and not widget._anchor_released:
609+
scroll_y = widget.scroll_y
610+
new_scroll_y = (
611+
arrange_result.spatial_map.total_region.bottom
612+
- (
613+
widget.container_size.height
614+
- widget.scrollbar_size_horizontal
615+
)
616+
)
617+
widget.set_reactive(Widget.scroll_y, new_scroll_y)
618+
widget.watch_scroll_y(scroll_y, new_scroll_y)
619+
608620
if visible_only:
609621
placements = arrange_result.get_visible_placements(
610622
sub_clip - child_region.offset + widget.scroll_offset

src/textual/screen.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,12 +1663,15 @@ def _watch__select_end(
16631663
if end_region.y <= start_region.bottom or self._box_select:
16641664
select_regions.append(Region.union(start_region, end_region))
16651665
else:
1666-
container_region = Region.from_union(
1667-
[
1668-
start_widget.select_container.content_region,
1669-
end_widget.select_container.content_region,
1670-
]
1671-
)
1666+
try:
1667+
container_region = Region.from_union(
1668+
[
1669+
start_widget.select_container.content_region,
1670+
end_widget.select_container.content_region,
1671+
]
1672+
)
1673+
except NoMatches:
1674+
return
16721675

16731676
start_region = Region.from_corners(
16741677
start_region.x,

src/textual/scrollbar.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,14 @@ async def _on_mouse_up(self, event: events.MouseUp) -> None:
357357

358358
def _on_mouse_capture(self, event: events.MouseCapture) -> None:
359359
if isinstance(self._parent, Widget):
360-
self._parent._user_scroll_interrupt = True
360+
self._parent.release_anchor()
361361
self.grabbed = event.mouse_position
362362
self.grabbed_position = self.position
363363

364364
def _on_mouse_release(self, event: events.MouseRelease) -> None:
365365
self.grabbed = None
366+
if self.vertical and isinstance(self.parent, Widget):
367+
self.parent._check_anchor()
366368
event.stop()
367369

368370
async def _on_mouse_move(self, event: events.MouseMove) -> None:

0 commit comments

Comments
 (0)