Repository: Textualize/textual

Issue:
Can't change a Select widget promt and options
```
yield Select(options = [("Opt 1", 1), ("Opt 2", 2)], id='select', allow_blank=False, prompt="select")
select_widget = self.query_one('#select')
```

These lines don't have effect:
```
select_widget._options = [("Opt 3", 1)]
select_widget.options = [("Opt 3", 1)]
select_widget.prompt = "select 2"
```

For the options, I can use `select_widget.set_options([("Opt 3", 1)])`, but I can't change the prompt and it's quite inconvenient




Discussion:
We found the following entries in the [FAQ](https://github.com/textualize/textual/blob/main/FAQ.md) which you may find helpful:


- [How do I center a widget in a screen?](https://github.com/textualize/textual/blob/main/FAQ.md#how-do-i-center-a-widget-in-a-screen)
- [How can I select and copy text in a Textual app?](https://github.com/textualize/textual/blob/main/FAQ.md#how-can-i-select-and-copy-text-in-a-textual-app)

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)
@ShinoNia Your report is a little unclear, so I want to check a couple of things. You say that this doesn't work:

```python
select_widget._options = [("Opt 3", 1)]
```

You're attempting to access an internal value there (denoted by the leading `_`); no public interface of Textual will have a heading `_` so that wouldn't be the way to do it.

You also say this doesn't work:

```python
select_widget.options = [("Opt 3", 1)]
```

which is correct. That isn't the documented method of updating the options for a `Select`.

As you appear to have discovered, [`set_options`](https://textual.textualize.io/widgets/select/#textual.widgets._select.Select.set_options) is the documented method for setting new options on a `Select`.

Can I just be clear here that you're not reporting anything to do with this?

Assuming you're not, would I be correct in understanding that the issue you're reporting here is that this:

```python
select_widget.prompt = "select 2"
```

has no visible effect? If so I agree and that would appear to be a bug that requires addressing.

@davep related to #2603?
@rodrigogiraoserrao Not if I'm reading it correctly. `Select.prompt` is more akin to `Input.placeholder`.

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
@@ -492,3 +492,15 @@ def clear(self) -> None:
             raise InvalidSelectValueError(
                 "Can't clear selection if allow_blank is set to False."
             ) from None
+
+    def _watch_prompt(self, prompt: str) -> None:
+        if not self.is_mounted:
+            return
+        select_current = self.query_one(SelectCurrent)
+        select_current.placeholder = prompt
+        if not self._allow_blank:
+            return
+        if self.value == self.BLANK:
+            select_current.update(self.BLANK)
+        option_list = self.query_one(SelectOverlay)
+        option_list.replace_option_prompt_at_index(0, Text(prompt, style="dim"))


Test patch:
diff --git a/tests/select/test_prompt.py b/tests/select/test_prompt.py
new file mode 100644
--- /dev/null
+++ b/tests/select/test_prompt.py
@@ -0,0 +1,54 @@
+from rich.text import Text
+
+from textual.app import App
+from textual.widgets import Select, Static
+from textual.widgets._select import SelectCurrent, SelectOverlay
+
+
+async def test_reactive_prompt_change():
+    """Regression test for https://github.com/Textualize/textual/issues/2983"""
+
+    class SelectApp(App):
+        def compose(self):
+            yield Select[int](
+                [(str(n), n) for n in range(3)],
+                prompt="Old prompt",
+            )
+
+    app = SelectApp()
+    async with app.run_test() as pilot:
+        select_widget = pilot.app.query_one(Select)
+        select_current = select_widget.query_one(SelectCurrent)
+        select_current_label = select_current.query_one("#label", Static)
+        select_overlay = select_widget.query_one(SelectOverlay)
+
+        assert select_current_label.renderable == Text("Old prompt")
+        assert select_overlay._options[0].prompt == Text("Old prompt")
+
+        select_widget.prompt = "New prompt"
+        assert select_current_label.renderable == Text("New prompt")
+        assert select_overlay._options[0].prompt == Text("New prompt")
+
+
+async def test_reactive_prompt_change_when_allow_blank_is_false():
+    class SelectApp(App):
+        def compose(self):
+            yield Select[int](
+                [(str(n), n) for n in range(3)],
+                prompt="Old prompt",
+                allow_blank=False,
+            )
+
+    app = SelectApp()
+    async with app.run_test() as pilot:
+        select_widget = pilot.app.query_one(Select)
+        select_current = select_widget.query_one(SelectCurrent)
+        select_current_label = select_current.query_one("#label", Static)
+        select_overlay = select_widget.query_one(SelectOverlay)
+
+        assert select_current_label.renderable == Text("0")
+        assert select_overlay._options[0].prompt == "0"
+
+        select_widget.prompt = "New prompt"
+        assert select_current_label.renderable == Text("0")
+        assert select_overlay._options[0].prompt == "0"