internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: Textualize/textual

Issue:
Incorrect type for DataTable.get_cell_coordinate column_key?
Was noticing mypy was giving me flack for using a `ColumnKey` in [DataTable.get_cell_coordinate](https://github.com/Textualize/textual/blob/b3efc3ffdb8d109c119873e8f09bfbe3de185a9a/src/textual/widgets/_data_table.py#L863) as the `column_key`. Looks to be typed as `Column`, but given the name and usage I'm guessing it should be a `ColumnKey`?

Thanks!


Discussion:

Resolving patch:
diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py
--- a/src/textual/widgets/_data_table.py
+++ b/src/textual/widgets/_data_table.py
@@ -860,7 +860,7 @@ def get_cell_at(self, coordinate: Coordinate) -> CellType:
         return self.get_cell(row_key, column_key)
 
     def get_cell_coordinate(
-        self, row_key: RowKey | str, column_key: Column | str
+        self, row_key: RowKey | str, column_key: ColumnKey | str
     ) -> Coordinate:
         """Given a row key and column key, return the corresponding cell coordinate.
 


Test patch:
Repository: Textualize/textual

Issue:
It is possible to give a widget an ID that can't be queried back
It is possible to give a widget an ID that can't then be queried back; for example:

```python
from textual.app import App, ComposeResult
from textual.widgets import Label

class BadIDApp(App[None]):

    def compose(self) -> ComposeResult:
        yield Label("Hello, World!", id="foo&")

    def on_mount(self) -> None:
        self.notify(f"{self.query_one('#foo&')}")

if __name__ == "__main__":
    BadIDApp().run()
```

Perhaps we should validate widget IDs when they are assigned?


Discussion:
We do this with class names, so it would be sensible to do it for IDs as well.

Resolving patch:
diff --git a/src/textual/dom.py b/src/textual/dom.py
--- a/src/textual/dom.py
+++ b/src/textual/dom.py
@@ -80,7 +80,7 @@ def check_identifiers(description: str, *names: str) -> None:
         description: Description of where identifier is used for error message.
         *names: Identifiers to check.
     """
-    match = _re_identifier.match
+    match = _re_identifier.fullmatch
     for name in names:
         if match(name) is None:
             raise BadIdentifier(


Test patch:
diff --git a/tests/test_dom.py b/tests/test_dom.py
--- a/tests/test_dom.py
+++ b/tests/test_dom.py
@@ -259,3 +259,24 @@ def test_walk_children_with_self_breadth(search):
     ]
 
     assert children == ["f", "e", "d", "c", "b", "a"]
+
+
+@pytest.mark.parametrize(
+    "identifier",
+    [
+        " bad",
+        "  terrible  ",
+        "worse!  ",
+        "&ampersand",
+        "amper&sand",
+        "ampersand&",
+        "2_leading_digits",
+        "água",  # water
+        "cão",  # dog
+        "@'/.23",
+    ],
+)
+def test_id_validation(identifier: str):
+    """Regression tests for https://github.com/Textualize/textual/issues/3954."""
+    with pytest.raises(BadIdentifier):
+        DOMNode(id=identifier)
Repository: Textualize/textual

Issue:
Add a `lock` to every widget.
We should add an `asyncio.Lock` to every widget, and a helpful docstring along with it.

It is possible for two tasks to call a single method, which can result in a race condition. This is easily fixable by adding `async with self.lock:`

Note that this is not an issue if methods on a widget are called from its own message handlers.

I'd be surprised if this task takes more than an hour!




Discussion:
Is this task to _add_ the lock attribute, period?
Or is the task asking to _add_ the lock attribute and then figure out if/where we should use it already?
It's just to add.

Resolving patch:
diff --git a/src/textual/widget.py b/src/textual/widget.py
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -4,7 +4,7 @@
 
 from __future__ import annotations
 
-from asyncio import create_task, wait
+from asyncio import Lock, create_task, wait
 from collections import Counter
 from fractions import Fraction
 from itertools import islice
@@ -373,6 +373,14 @@ def __init__(
         if self.BORDER_SUBTITLE:
             self.border_subtitle = self.BORDER_SUBTITLE
 
+        self.lock = Lock()
+        """`asyncio` lock to be used to synchronize the state of the widget.
+
+        Two different tasks might call methods on a widget at the same time, which
+        might result in a race condition.
+        This can be fixed by adding `async with widget.lock:` around the method calls.
+        """
+
     virtual_size: Reactive[Size] = Reactive(Size(0, 0), layout=True)
     """The virtual (scrollable) [size][textual.geometry.Size] of the widget."""
 
diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py
--- a/src/textual/widgets/_tree.py
+++ b/src/textual/widgets/_tree.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-from asyncio import Lock
 from dataclasses import dataclass
 from typing import TYPE_CHECKING, ClassVar, Generic, Iterable, NewType, TypeVar, cast
 
@@ -618,8 +617,6 @@ def __init__(
         self._line_cache: LRUCache[LineCacheKey, Strip] = LRUCache(1024)
         self._tree_lines_cached: list[_TreeLine[TreeDataType]] | None = None
         self._cursor_node: TreeNode[TreeDataType] | None = None
-        self.lock = Lock()
-        """Used to synchronise stateful directory tree operations."""
 
         super().__init__(name=name, id=id, classes=classes, disabled=disabled)
 


Test patch:
Repository: Textualize/textual

Issue:
SyntaxWarning for loading indicator widget
I receive this warning after upgrading to `0.52.0`:

```
/Users/cthompson/Library/Caches/pypoetry/virtualenvs/dolphie-z84eXs3q-py3.11/lib/python3.11/site-packages/textual/widgets/_loading_indicator.py:57: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if self.app.animation_level is "none":
```

https://github.com/Textualize/textual/blob/main/src/textual/widgets/_loading_indicator.py#L57

Seems we just need to change `is "none"` to `== "none"`


Discussion:
Let's fix this and get a patch release out today
On it.

Resolving patch:
diff --git a/src/textual/widgets/_loading_indicator.py b/src/textual/widgets/_loading_indicator.py
--- a/src/textual/widgets/_loading_indicator.py
+++ b/src/textual/widgets/_loading_indicator.py
@@ -54,7 +54,7 @@ def _on_mount(self, _: Mount) -> None:
         self.auto_refresh = 1 / 16
 
     def render(self) -> RenderableType:
-        if self.app.animation_level is "none":
+        if self.app.animation_level == "none":
             return Text("Loading...")
 
         elapsed = time() - self._start_time


Test patch:
Repository: Textualize/textual

Issue:
Errors in devtools on startup - possibly related to tooltip cleanup
I can't reproduce this, but I on launching a Textual app on the `main` branch I got a bunch of errors:

```
error publishing signal to Screen(id='_default') ignored; No widget under screen coordinate (110, 12)
```

I think these arrived before I interacted with the code at all. The code didn't explicitly use any tooltip functionality.

From @davep in Slack: I take it back, it will be that [tooltip clearing] code. `Screen._maybe_clear_tooltip` should catch whatever exception `get_widget_at throws`; that’ll be it. Name in log still a good idea though.




Discussion:
>Name in log still a good idea though.

This part referring to the fact that `Signal` has a `name` but the code that logs the error isn't including the name on the log.

Resolving patch:
diff --git a/src/textual/screen.py b/src/textual/screen.py
--- a/src/textual/screen.py
+++ b/src/textual/screen.py
@@ -289,6 +289,9 @@ def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
 
         Returns:
             Widget and screen region.
+
+        Raises:
+            NoWidget: If there is no widget under the screen coordinate.
         """
         return self._compositor.get_widget_at(x, y)
 
@@ -886,11 +889,15 @@ def _maybe_clear_tooltip(self) -> None:
         # If there's a widget associated with the tooltip at all...
         if self._tooltip_widget is not None:
             # ...look at what's currently under the mouse.
-            under_mouse, _ = self.get_widget_at(*self.app.mouse_position)
-            # If it's not the same widget...
-            if under_mouse is not self._tooltip_widget:
-                # ...clear the tooltip.
-                self._clear_tooltip()
+            try:
+                under_mouse, _ = self.get_widget_at(*self.app.mouse_position)
+            except NoWidget:
+                pass
+            else:
+                # If it's not the same widget...
+                if under_mouse is not self._tooltip_widget:
+                    # ...clear the tooltip.
+                    self._clear_tooltip()
 
     def _handle_tooltip_timer(self, widget: Widget) -> None:
         """Called by a timer from _handle_mouse_move to update the tooltip.


Test patch:
Repository: Textualize/textual

Issue:
Type warning with `AwaitComplete`
It seems that there is a type error when it comes to awaiting `AwaitComplete`. As an example, given this code:

```python
from textual.app import App
from textual.widgets import TabbedContent, TabPane

class AwaitableTypeWarningApp(App[None]):

    async def on_mount(self) -> None:
        await self.query_one(TabbedContent).add_pane(TabPane("Test"))
        await self.query_one(TabbedContent).remove_pane("some-tab")
```

pyright reports:

```
/Users/davep/develop/python/textual-sandbox/await_type_warning.py
  /Users/davep/develop/python/textual-sandbox/await_type_warning.py:7:15 - error: "AwaitComplete" is not awaitable
    "AwaitComplete" is incompatible with protocol "Awaitable[_T_co@Awaitable]"
      "__await__" is an incompatible type
        Type "() -> Iterator[None]" cannot be assigned to type "() -> Generator[Any, None, _T_co@Awaitable]"
          Function return type "Iterator[None]" is incompatible with type "Generator[Any, None, _T_co@Awaitable]"
            "Iterator[None]" is incompatible with "Generator[Any, None, _T_co@Awaitable]" (reportGeneralTypeIssues)
  /Users/davep/develop/python/textual-sandbox/await_type_warning.py:8:15 - error: "AwaitComplete" is not awaitable
    "AwaitComplete" is incompatible with protocol "Awaitable[_T_co@Awaitable]"
      "__await__" is an incompatible type
        Type "() -> Iterator[None]" cannot be assigned to type "() -> Generator[Any, None, _T_co@Awaitable]"
          Function return type "Iterator[None]" is incompatible with type "Generator[Any, None, _T_co@Awaitable]"
            "Iterator[None]" is incompatible with "Generator[Any, None, _T_co@Awaitable]" (reportGeneralTypeIssues)
2 errors, 0 warnings, 0 informations 
```


Discussion:
I wonder if `__await__` should return `Iterator[Any]` instead of `Iterator[None]`. I have no idea how to actually type this protocol...

Resolving patch:
diff --git a/src/textual/await_complete.py b/src/textual/await_complete.py
--- a/src/textual/await_complete.py
+++ b/src/textual/await_complete.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from asyncio import Future, gather
-from typing import Any, Coroutine, Iterator, TypeVar
+from typing import Any, Coroutine, Generator, TypeVar
 
 import rich.repr
 
@@ -19,12 +19,12 @@ def __init__(self, *coroutines: Coroutine[Any, Any, Any]) -> None:
             coroutines: One or more coroutines to execute.
         """
         self.coroutines: tuple[Coroutine[Any, Any, Any], ...] = coroutines
-        self._future: Future = gather(*self.coroutines)
+        self._future: Future[Any] = gather(*self.coroutines)
 
     async def __call__(self) -> Any:
         return await self
 
-    def __await__(self) -> Iterator[None]:
+    def __await__(self) -> Generator[Any, None, Any]:
         return self._future.__await__()
 
     @property


Test patch:
Repository: Textualize/textual

Issue:
`Switch.action_toggle` name clash
Similar to #4214 `Switch` has an `action_toggle` method whose name and form clash with `DOMNode.action_toggle`.


Discussion:

Resolving patch:
diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py
--- a/src/textual/widgets/_switch.py
+++ b/src/textual/widgets/_switch.py
@@ -26,7 +26,7 @@ class Switch(Widget, can_focus=True):
     """
 
     BINDINGS: ClassVar[list[BindingType]] = [
-        Binding("enter,space", "toggle", "Toggle", show=False),
+        Binding("enter,space", "toggle_switch", "Toggle", show=False),
     ]
     """
     | Key(s) | Description |
@@ -164,7 +164,7 @@ async def _on_click(self, event: Click) -> None:
         event.stop()
         self.toggle()
 
-    def action_toggle(self) -> None:
+    def action_toggle_switch(self) -> None:
         """Toggle the state of the switch."""
         self.toggle()
 


Test patch:
diff --git a/tests/animations/test_switch_animation.py b/tests/animations/test_switch_animation.py
--- a/tests/animations/test_switch_animation.py
+++ b/tests/animations/test_switch_animation.py
@@ -21,7 +21,7 @@ async def test_switch_animates_on_full() -> None:
         animator = app.animator
         # Freeze time at 0 before triggering the animation.
         animator._get_time = lambda *_: 0
-        switch.action_toggle()
+        switch.action_toggle_switch()
         await pilot.pause()
         # Freeze time after the animation start and before animation end.
         animator._get_time = lambda *_: 0.01
@@ -40,7 +40,7 @@ async def test_switch_animates_on_basic() -> None:
         animator = app.animator
         # Freeze time at 0 before triggering the animation.
         animator._get_time = lambda *_: 0
-        switch.action_toggle()
+        switch.action_toggle_switch()
         await pilot.pause()
         # Freeze time after the animation start and before animation end.
         animator._get_time = lambda *_: 0.01
@@ -59,7 +59,7 @@ async def test_switch_does_not_animate_on_none() -> None:
         animator = app.animator
         # Freeze time at 0 before triggering the animation.
         animator._get_time = lambda *_: 0
-        switch.action_toggle()
+        switch.action_toggle_switch()
         await pilot.pause()
         # Freeze time after the animation start and before animation end.
         animator._get_time = lambda *_: 0.01
Repository: Textualize/textual

Issue:
`Select` `OptionHighlighted` message leakage
The `Select` widget makes use of `OptionList`; when the user opens the `Select` to pick a value, `OptionHighlighted` messages "leak" out of the `Select`. This could result in unexpected messages for the developer who has an `OptionList` and a `Select` in their DOM and who doesn't expect to actually have two `OptionList`s that might send messages.

I think we should either stop the `OptionHighlighted` from bubbling outside of the `Select`, or we should turn them into `SelectionHighlighted` if it's felt necessary that they can be handled.

To illustrate:

```python
from textual import on
from textual.app import App, ComposeResult
from textual.widgets import OptionList, Select, Log

class LeakySelectApp(App[None]):

    def compose(self) -> ComposeResult:
        yield Log()
        yield OptionList(*(f"This is option {n}" for n in range(10)))
        yield Select[int](((f"This is selection {n}", n) for n in range(10)))

    @on(OptionList.OptionHighlighted)
    def log_some_option(self, event: OptionList.OptionHighlighted) -> None:
        self.query_one(Log).write_line(f"{event}")

if __name__ == "__main__":
    LeakySelectApp().run()
```

I think the dev would reasonably expect to *only* get calls to `log_some_option` from the `OptionList` they composed in.


Discussion:
I think we should quell those messages. I can't see a use for an additional message that reports what the user has currently selected.

Resolving patch:
diff --git a/src/textual/widgets/_select.py b/src/textual/widgets/_select.py
--- a/src/textual/widgets/_select.py
+++ b/src/textual/widgets/_select.py
@@ -95,6 +95,12 @@ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> No
         event.stop()
         self.post_message(self.UpdateSelection(event.option_index))
 
+    def on_option_list_option_highlighted(
+        self, event: OptionList.OptionHighlighted
+    ) -> None:
+        """Stop option list highlighted messages leaking."""
+        event.stop()
+
 
 class SelectCurrent(Horizontal):
     """Displays the currently selected option."""


Test patch:
Repository: Textualize/textual

Issue:
Hiding a `TextArea` while mouse-based selection is happening can trap the cursor
*Initially discovered and reported in a comment in #4274 by @TomJGooding*

Somewhat following on from #4274, although very likely an easier fix (I think it just needs a `Hide` handler to release the mouse and do any other housekeeping necessary to `TextArea` selection), if you start a mouse selection within `TextArea` and then hide the `TextArea`, the mouse will be trapped and won't respond anywhere else.

To illustrate:

```python
from pathlib import Path
from textual.app import App, ComposeResult
from textual.widgets import Collapsible, TextArea


class GreedyScrollbarApp(App[None]):
    BINDINGS = [("f1", "collapse")]

    CSS = """
    TextArea {
        height: 10;
    }
    """

    def compose(self) -> ComposeResult:
        for n in range(5):
            with Collapsible(title=f"This is collapsible #{n}", collapsed=bool(n)):
                yield TextArea(Path(__file__).read_text(), language="python")

    def action_collapse(self) -> None:
        target.collapsed = not (target := self.query(Collapsible).first()).collapsed


if __name__ == "__main__":
    GreedyScrollbarApp().run()
```

Start a mouse selection in the first `TextArea`, press <kbd>F1</kbd> while still selecting with the mouse, then notice nothing else will respond to mouse clicks.




Discussion:

Resolving patch:
diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py
--- a/src/textual/widgets/_text_area.py
+++ b/src/textual/widgets/_text_area.py
@@ -1498,13 +1498,21 @@ async def _on_mouse_move(self, event: events.MouseMove) -> None:
             selection_start, _ = self.selection
             self.selection = Selection(selection_start, target)
 
-    async def _on_mouse_up(self, event: events.MouseUp) -> None:
+    def _end_mouse_selection(self) -> None:
         """Finalize the selection that has been made using the mouse."""
         self._selecting = False
         self.release_mouse()
         self.record_cursor_width()
         self._restart_blink()
 
+    async def _on_mouse_up(self, event: events.MouseUp) -> None:
+        """Finalize the selection that has been made using the mouse."""
+        self._end_mouse_selection()
+
+    async def _on_hide(self, event: events.Hide) -> None:
+        """Finalize the selection that has been made using the mouse when thew widget is hidden."""
+        self._end_mouse_selection()
+
     async def _on_paste(self, event: events.Paste) -> None:
         """When a paste occurs, insert the text from the paste event into the document."""
         if self.read_only:


Test patch:
Repository: Textualize/textual

Issue:
Update SelectionToggled docs
I think the note in the [SelectionToggled docs](https://textual.textualize.io/widgets/selection_list/#textual.widgets._selection_list.SelectionList.SelectionToggled) that it "is only sent if the selection is toggled by user interaction" might need updating following the work done in #4256.


Discussion:
Thank you for your issue. Give us a little time to review it.

PS. You might want to check the [FAQ](https://textual.textualize.io/FAQ/) if you haven't done so already.

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)

Resolving patch:
diff --git a/src/textual/widgets/_selection_list.py b/src/textual/widgets/_selection_list.py
--- a/src/textual/widgets/_selection_list.py
+++ b/src/textual/widgets/_selection_list.py
@@ -204,22 +204,14 @@ class SelectionToggled(SelectionMessage[MessageSelectionType]):
 
         Can be handled using `on_selection_list_selection_toggled` in a subclass of
         [`SelectionList`][textual.widgets.SelectionList] or in a parent node in the DOM.
-
-        Note:
-            This message is only sent if the selection is toggled by user
-            interaction. See
-            [`SelectedChanged`][textual.widgets.SelectionList.SelectedChanged]
-            for a message sent when any change (selected or deselected,
-            either by user interaction or by API calls) is made to the
-            selected values.
         """
 
     @dataclass
     class SelectedChanged(Generic[MessageSelectionType], Message):
         """Message sent when the collection of selected values changes.
 
-        This message is sent when any change to the collection of selected
-        values takes place; either by user interaction or by API calls.
+        Can be handled using `on_selection_list_selected_changed` in a subclass of
+        [`SelectionList`][textual.widgets.SelectionList] or in a parent node in the DOM.
         """
 
         selection_list: SelectionList[MessageSelectionType]


Test patch:
Repository: Textualize/textual

Issue:
Docs change - use `pane` instead of `tab` for an event listener on tab change
We tried adding an event listener for a tab change using the docs here, but couldn't get it to work: https://textual.textualize.io/guide/events/#applying-css-selectors-to-arbitrary-attributes. Should the docs be updated to use `pane` instead of `tab`, or are we doing something wrong?  Specifically this snippet:

```python
@on(TabbedContent.TabActivated, tab="#home")
def home_tab(self) -> None:
    self.log("Switched back to home tab.")
```

I got it working, I think it's related to the breaking change described in https://github.com/Textualize/textual/blob/main/CHANGELOG.md#0460---2023-12-17

> Breaking change: tab is no longer a @on decorator selector for TabbedContent.TabActivated -- use pane instead https://github.com/Textualize/textual/pull/3815


Discussion:
We found the following entries in the [FAQ](https://textual.textualize.io/FAQ/) which you may find helpful:


- [Why doesn't Textual look good on macOS?](https://textual.textualize.io/FAQ/#why-doesnt-textual-look-good-on-macos)
- [How do I center a widget in a screen?](https://textual.textualize.io/FAQ/#how-do-i-center-a-widget-in-a-screen)

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)

Resolving patch:
diff --git a/src/textual/_on.py b/src/textual/_on.py
--- a/src/textual/_on.py
+++ b/src/textual/_on.py
@@ -43,7 +43,7 @@ def quit_button(self) -> None:
     Example:
         ```python
         # Handle the activation of the tab "#home" within the `TabbedContent` "#tabs".
-        @on(TabbedContent.TabActivated, "#tabs", tab="#home")
+        @on(TabbedContent.TabActivated, "#tabs", pane="#home")
         def switch_to_home(self) -> None:
             self.log("Switching back to the home tab.")
             ...


Test patch: