Skip to content

Commit 3e1519c

Browse files
committed
Merge branch 'main' into refactor-label-rename-renderable-to-content
2 parents 8a48f9f + 47d0507 commit 3e1519c

File tree

16 files changed

+552
-43
lines changed

16 files changed

+552
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1717
### Added
1818

1919
- Added `bar_renderable` to `ProgressBar` widget https://github.com/Textualize/textual/pull/5963
20-
20+
- Added `OptionList.set_options` https://github.com/Textualize/textual/pull/6048
21+
- Added `TextArea.suggestion` https://github.com/Textualize/textual/pull/6048
22+
- Added `TextArea.placeholder` https://github.com/Textualize/textual/pull/6048
2123

2224
### Changed
2325

src/textual/_queue.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
from asyncio import Event
5+
from collections import deque
6+
from typing import Generic, TypeVar
7+
8+
QueueType = TypeVar("QueueType")
9+
10+
11+
class Queue(Generic[QueueType]):
12+
"""A cut-down version of asyncio.Queue
13+
14+
This has just enough functionality to run the message pumps.
15+
16+
"""
17+
18+
def __init__(self) -> None:
19+
self.values: deque[QueueType] = deque()
20+
self.ready_event = Event()
21+
22+
def put_nowait(self, value: QueueType) -> None:
23+
self.values.append(value)
24+
self.ready_event.set()
25+
26+
def qsize(self) -> int:
27+
return len(self.values)
28+
29+
def empty(self) -> bool:
30+
return not self.values
31+
32+
def task_done(self) -> None:
33+
pass
34+
35+
async def get(self) -> QueueType:
36+
if not self.ready_event.is_set():
37+
await self.ready_event.wait()
38+
value = self.values.popleft()
39+
if not self.values:
40+
self.ready_event.clear()
41+
return value
42+
43+
def get_nowait(self) -> QueueType:
44+
if not self.values:
45+
raise asyncio.QueueEmpty()
46+
value = self.values.popleft()
47+
if not self.values:
48+
self.ready_event.clear()
49+
return value

src/textual/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,6 +2768,7 @@ def push_screen(
27682768

27692769
next_screen._push_result_callback(message_pump, callback, future)
27702770
self._load_screen_css(next_screen)
2771+
next_screen._update_auto_focus()
27712772
self._screen_stack.append(next_screen)
27722773
next_screen.post_message(events.ScreenResume())
27732774
self.log.system(f"{self.screen} is current (PUSHED)")

src/textual/dom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ def _update_styles(self) -> None:
17291729
17301730
Should be called whenever CSS classes / pseudo classes change.
17311731
"""
1732-
if not self.is_attached:
1732+
if not self.is_attached or not self.screen.is_mounted:
17331733
return
17341734
try:
17351735
self.app.update_styles(self)

src/textual/highlight.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class HighlightTheme:
1919
STYLES: dict[TokenType, str] = {
2020
Token.Comment: "$text 60%",
2121
Token.Error: "$text-error on $error-muted",
22+
Token.Generic.Strong: "bold",
23+
Token.Generic.Emph: "italic",
2224
Token.Generic.Error: "$text-error on $error-muted",
2325
Token.Generic.Heading: "$text-primary underline",
2426
Token.Generic.Subheading: "$text-primary",
@@ -27,6 +29,7 @@ class HighlightTheme:
2729
Token.Keyword.Namespace: "$text-error",
2830
Token.Keyword.Type: "bold",
2931
Token.Literal.Number: "$text-warning",
32+
Token.Literal.String.Backtick: "$text 60%",
3033
Token.Literal.String: "$text-success 90%",
3134
Token.Literal.String.Doc: "$text-success 80% italic",
3235
Token.Literal.String.Double: "$text-success 90%",

src/textual/message_pump.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from textual._context import message_hook as message_hook_context_var
3737
from textual._context import prevent_message_types_stack
3838
from textual._on import OnNoWidget
39+
from textual._queue import Queue
3940
from textual._time import time
4041
from textual.constants import SLOW_THRESHOLD
4142
from textual.css.match import match
@@ -143,8 +144,8 @@ def __init__(self, parent: MessagePump | None = None) -> None:
143144
"""
144145

145146
@cached_property
146-
def _message_queue(self) -> asyncio.Queue[Message | None]:
147-
return asyncio.Queue()
147+
def _message_queue(self) -> Queue[Message | None]:
148+
return Queue()
148149

149150
@cached_property
150151
def _mounted_event(self) -> asyncio.Event:

src/textual/screen.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,23 +1346,31 @@ def _on_screen_resume(self) -> None:
13461346
self.app._refresh_notifications()
13471347
size = self.app.size
13481348

1349-
# Only auto-focus when the app has focus (textual-web only)
1349+
self._update_auto_focus()
1350+
1351+
if self.is_attached:
1352+
self._compositor_refresh()
1353+
self.app.stylesheet.update(self)
1354+
self._refresh_layout(size)
1355+
self.refresh()
1356+
1357+
async def _compose(self) -> None:
1358+
await super()._compose()
1359+
self._update_auto_focus()
1360+
1361+
def _update_auto_focus(self) -> None:
1362+
"""Update auto focus."""
13501363
if self.app.app_focus:
13511364
auto_focus = (
13521365
self.app.AUTO_FOCUS if self.AUTO_FOCUS is None else self.AUTO_FOCUS
13531366
)
13541367
if auto_focus and self.focused is None:
13551368
for widget in self.query(auto_focus):
13561369
if widget.focusable:
1370+
widget.has_focus = True
13571371
self.set_focus(widget)
13581372
break
13591373

1360-
if self.is_attached:
1361-
self._compositor_refresh()
1362-
self.app.stylesheet.update(self)
1363-
self._refresh_layout(size)
1364-
self.refresh()
1365-
13661374
def _on_screen_suspend(self) -> None:
13671375
"""Screen has suspended."""
13681376
if self.app.SUSPENDED_SCREEN_CLASS:

src/textual/widget.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3904,12 +3904,12 @@ def post_render(
39043904

39053905
return renderable
39063906

3907-
def watch_mouse_hover(self, value: bool) -> None:
3907+
def watch_mouse_hover(self, _mouse_over: bool) -> None:
39083908
"""Update from CSS if mouse over state changes."""
39093909
if self._has_hover_style:
39103910
self._update_styles()
39113911

3912-
def watch_has_focus(self, value: bool) -> None:
3912+
def watch_has_focus(self, _has_focus: bool) -> None:
39133913
"""Update from CSS if has focus state changes."""
39143914
self._update_styles()
39153915

src/textual/widgets/_option_list.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,15 +324,37 @@ def clear_options(self) -> Self:
324324
self._option_to_index.clear()
325325
self.highlighted = None
326326
self.refresh()
327-
self.scroll_to(0, 0, animate=False)
327+
self.scroll_y = 0
328328
self._update_lines()
329329
return self
330330

331+
def set_options(self, options: Iterable[OptionListContent]) -> Self:
332+
"""Set options, potentially clearing existing options.
333+
334+
Args:
335+
options: Options to set.
336+
337+
Returns:
338+
The `OptionList` instance.
339+
"""
340+
self._options.clear()
341+
self._line_cache.clear()
342+
self._option_render_cache.clear()
343+
self._id_to_option.clear()
344+
self._option_to_index.clear()
345+
self.highlighted = None
346+
self.scroll_y = 0
347+
self.add_options(options)
348+
return self
349+
331350
def add_options(self, new_options: Iterable[OptionListContent]) -> Self:
332351
"""Add new options.
333352
334353
Args:
335354
new_options: Content of new options.
355+
356+
Returns:
357+
The `OptionList` instance.
336358
"""
337359

338360
new_options = list(new_options)

src/textual/widgets/_static.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ class Static(Widget, inherit_bindings=False):
2929
}
3030
"""
3131

32-
_renderable: VisualType
33-
3432
def __init__(
3533
self,
3634
content: VisualType = "",
@@ -48,23 +46,31 @@ def __init__(
4846
)
4947
self.expand = expand
5048
self.shrink = shrink
51-
self._content = content
52-
self._visual: Visual | None = None
49+
self.__content = content
50+
self.__visual: Visual | None = None
5351

5452
@property
5553
def visual(self) -> Visual:
56-
if self._visual is None:
57-
self._visual = visualize(self, self._content, markup=self._render_markup)
58-
return self._visual
54+
"""The visual to be displayed.
55+
56+
Note that the visual is what is ultimately rendered in the widget, but may not be the
57+
same object set with the `update` method or `content` property. For instance, if you
58+
update with a string, then the visual will be a [Content][textual.content.Content] instance.
59+
60+
"""
61+
if self.__visual is None:
62+
self.__visual = visualize(self, self.__content, markup=self._render_markup)
63+
return self.__visual
5964

6065
@property
6166
def content(self) -> VisualType:
62-
"""The content set in the constructor."""
63-
return self._content
67+
"""The original content set in the constructor."""
68+
return self.__content
6469

6570
@content.setter
6671
def content(self, content: VisualType) -> None:
67-
self._visual = visualize(self, content, markup=self._render_markup)
72+
self.__content = content
73+
self.__visual = visualize(self, content, markup=self._render_markup)
6874
self.clear_cached_dimensions()
6975
self.refresh(layout=True)
7076

@@ -77,13 +83,13 @@ def render(self) -> RenderResult:
7783
return self.visual
7884

7985
def update(self, content: VisualType = "", *, layout: bool = True) -> None:
80-
"""Update the widget's content area with new text or Rich renderable.
86+
"""Update the widget's content area with a string, a Visual (such as [Content][textual.content.Content]), or a [Rich renderable](https://rich.readthedocs.io/en/latest/protocol.html).
8187
8288
Args:
8389
content: New content.
84-
layout: Also perform a layout operation (set to `False` if you are certain the size won't change.)
90+
layout: Also perform a layout operation (set to `False` if you are certain the size won't change).
8591
"""
8692

87-
self._content = content
88-
self._visual = visualize(self, content, markup=self._render_markup)
93+
self.__content = content
94+
self.__visual = visualize(self, content, markup=self._render_markup)
8995
self.refresh(layout=layout)

0 commit comments

Comments
 (0)