Skip to content

Commit

Permalink
Merge branch 'main' into less-busy-suggest
Browse files Browse the repository at this point in the history
  • Loading branch information
davep committed Mar 26, 2024
2 parents 543881e + d55410c commit 17ca14d
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 27 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed

- Fixed a crash in `TextArea` when undoing an edit to a selection the selection was made backwards https://github.com/Textualize/textual/issues/4301
- Fixed issue with flickering scrollbars https://github.com/Textualize/textual/pull/4315
- Fixed issue where narrow TextArea would repeatedly wrap due to scrollbar appearing/disappearing https://github.com/Textualize/textual/pull/4334
- Fix progress bar ETA not updating when setting `total` reactive https://github.com/Textualize/textual/pull/4316

### Changed

- ProgressBar won't show ETA until there is at least one second of samples https://github.com/Textualize/textual/pull/4316

## [0.53.1] - 2023-03-18
Expand Down
2 changes: 1 addition & 1 deletion src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .css.parse import parse_selectors
from .css.query import NoMatches, QueryType
from .dom import DOMNode
from .errors import NoWidget
from .geometry import Offset, Region, Size
from .reactive import Reactive
from .renderables.background_screen import BackgroundScreen
Expand All @@ -54,7 +55,6 @@
from .command import Provider

# Unused & ignored imports are needed for the docs to link to these objects:
from .errors import NoWidget # type: ignore # noqa: F401
from .message_pump import MessagePump

# Screen updates will be batched so that they don't happen more often than 60 times per second:
Expand Down
1 change: 1 addition & 0 deletions src/textual/scroll_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def _size_updated(
or virtual_size != self.virtual_size
or container_size != self.container_size
):
self._scrollbar_changes.clear()
self._size = size
virtual_size = self.virtual_size
self._container_size = size - self.styles.gutter.totals
Expand Down
4 changes: 3 additions & 1 deletion src/textual/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,6 @@ def publish(self) -> None:
try:
callback()
except Exception as error:
log.error(f"error publishing signal to {node} ignored; {error}")
log.error(
f"error publishing signal to {node} ignored (callback={callback}); {error}"
)
49 changes: 27 additions & 22 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,14 +361,15 @@ def __init__(

self._styles_cache = StylesCache()
self._rich_style_cache: dict[str, tuple[Style, Style]] = {}
self._stabilize_scrollbar: tuple[Size, str, str] | None = None
"""Used to prevent scrollbar logic getting stuck in an infinite loop."""

self._tooltip: RenderableType | None = None
"""The tooltip content."""
self._absolute_offset: Offset | None = None
"""Force an absolute offset for the widget (used by tooltips)."""

self._scrollbar_changes: set[tuple[bool, bool]] = set()
"""Used to stabilize scrollbars."""

super().__init__(
name=name,
id=id,
Expand Down Expand Up @@ -799,7 +800,6 @@ def _arrange(self, size: Size) -> DockArrangeResult:
def _clear_arrangement_cache(self) -> None:
"""Clear arrangement cache, forcing a new arrange operation."""
self._arrangement_cache.clear()
self._stabilize_scrollbar = None

def _get_virtual_dom(self) -> Iterable[Widget]:
"""Get widgets not part of the DOM.
Expand Down Expand Up @@ -1437,14 +1437,6 @@ def _refresh_scrollbars(self) -> None:
overflow_x = styles.overflow_x
overflow_y = styles.overflow_y

stabilize_scrollbar = (
self.container_size,
overflow_x,
overflow_y,
)
if self._stabilize_scrollbar == stabilize_scrollbar:
return

width, height = self._container_size

show_horizontal = False
Expand All @@ -1463,17 +1455,31 @@ def _refresh_scrollbars(self) -> None:
elif overflow_y == "auto":
show_vertical = self.virtual_size.height > height

# When a single scrollbar is shown, the other dimension changes, so we need to recalculate.
if overflow_x == "auto" and show_vertical and not show_horizontal:
show_horizontal = self.virtual_size.width > (
width - styles.scrollbar_size_vertical
)
if overflow_y == "auto" and show_horizontal and not show_vertical:
show_vertical = self.virtual_size.height > (
height - styles.scrollbar_size_horizontal
)
_show_horizontal = show_horizontal
_show_vertical = show_vertical

self._stabilize_scrollbar = stabilize_scrollbar
if not (
overflow_x == "auto"
and overflow_y == "auto"
and (show_horizontal, show_vertical) in self._scrollbar_changes
):
# When a single scrollbar is shown, the other dimension changes, so we need to recalculate.
if overflow_x == "auto" and show_vertical and not show_horizontal:
show_horizontal = self.virtual_size.width > (
width - styles.scrollbar_size_vertical
)
if overflow_y == "auto" and show_horizontal and not show_vertical:
show_vertical = self.virtual_size.height > (
height - styles.scrollbar_size_horizontal
)

if (
self.show_horizontal_scrollbar != show_horizontal
or self.show_vertical_scrollbar != show_vertical
):
self._scrollbar_changes.add((_show_horizontal, _show_vertical))
else:
self._scrollbar_changes.clear()

self.show_horizontal_scrollbar = show_horizontal
self.show_vertical_scrollbar = show_vertical
Expand Down Expand Up @@ -3322,7 +3328,6 @@ def refresh(
return self
if layout:
self._layout_required = True
self._stabilize_scrollbar = None
for ancestor in self.ancestors:
if not isinstance(ancestor, Widget):
break
Expand Down
8 changes: 7 additions & 1 deletion src/textual/widgets/_markdown.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import re
from pathlib import Path, PurePath
from typing import Callable, Iterable, Optional

Expand Down Expand Up @@ -149,7 +150,12 @@ def build_from_token(self, token: Token) -> None:
if token.children:
for child in token.children:
if child.type == "text":
content.append(child.content, style_stack[-1])
content.append(
# Ensure repeating spaces and/or tabs get squashed
# down to a single space.
re.sub(r"\s+", " ", child.content),
style_stack[-1],
)
if child.type == "hardbreak":
content.append("\n")
if child.type == "softbreak":
Expand Down
2 changes: 1 addition & 1 deletion src/textual/widgets/_sparkline.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __init__(
Args:
data: The initial data to populate the sparkline with.
summary_function: Summarises bar values into a single value used to
summary_function: Summarizes bar values into a single value used to
represent each bar.
name: The name of the widget.
id: The ID of the widget in the DOM.
Expand Down
3 changes: 2 additions & 1 deletion src/textual/widgets/_text_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,8 @@ def _watch_indent_width(self) -> None:
self.scroll_cursor_visible()

def _watch_show_vertical_scrollbar(self) -> None:
self._rewrap_and_refresh_virtual_size()
if self.wrap_width:
self._rewrap_and_refresh_virtual_size()
self.scroll_cursor_visible()

def _watch_theme(self, theme: str) -> None:
Expand Down
330 changes: 330 additions & 0 deletions tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions tests/snapshot_tests/snapshot_apps/markdown_whitespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.widgets import Markdown

MARKDOWN = (
"""\
X X
X X
X\tX
X\t\tX
""",
"""\
X \tX
X \t \tX
""",
"""\
[X X X\tX\t\tX \t \tX](https://example.com/)
_X X X\tX\t\tX \t \tX_
**X X X\tX\t\tX \t \tX**
~~X X X\tX\t\tX \t \tX~~
"""
)

class MarkdownSpaceApp(App[None]):

CSS = """
Markdown {
margin-left: 0;
border-left: solid red;
width: 1fr;
height: 1fr;
}
.code {
height: 2fr;
border-top: solid red;
}
"""

def compose(self) -> ComposeResult:
with Horizontal():
for document in MARKDOWN:
yield Markdown(document)
yield Markdown("""```python
# Two spaces: see?
class Foo:
'''This is a doc string.'''
some_code(1, 2, 3, 4)
```
""", classes="code")

if __name__ == "__main__":
MarkdownSpaceApp().run()
10 changes: 10 additions & 0 deletions tests/snapshot_tests/snapshot_apps/welcome_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from textual.app import App, ComposeResult
from textual.widgets import Welcome

class WelcomeApp(App[None]):

def compose(self) -> ComposeResult:
yield Welcome()

if __name__ == "__main__":
WelcomeApp().run()
8 changes: 8 additions & 0 deletions tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@ async def run_before(pilot):
run_before=run_before,
)

def test_markdown_space_squashing(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "markdown_whitespace.py")


def test_layer_fix(snap_compare):
# Check https://github.com/Textualize/textual/issues/1358
Expand Down Expand Up @@ -1162,3 +1165,8 @@ def test_button_widths(snap_compare):
"""Test that button widths expand auto containers as expected."""
# https://github.com/Textualize/textual/issues/4024
assert snap_compare(SNAPSHOT_APPS_DIR / "button_widths.py")


def test_welcome(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "welcome_widget.py")

0 comments on commit 17ca14d

Please sign in to comment.