Repository: Textualize/textual

Issue:
`ListView` initial index is off by one
Setting the `initial_index` of a `ListView` seems to be off by one, introduced somewhere in v0.48.2:

```python
from textual.app import App, ComposeResult
from textual.widgets import Label, ListItem, ListView


class ExampleApp(App):
    def compose(self) -> ComposeResult:
        list_items = [ListItem(Label(str(i))) for i in range(10)]
        yield ListView(
            *list_items,
            initial_index=9,
        )


if __name__ == "__main__":
    app = ExampleApp()
    app.run()
```

![image](https://github.com/Textualize/textual/assets/101601846/90471154-e20b-4da4-b776-f436a29dfac2)

Discussion:
TomJGooding: `mypy` complains that `Argument 1 to "find_next_enabled" has incompatible type "tuple[ListItem, ...]"; expected "Sequence[Disableable]"`, but it also complains about the `NodeList` so I'm not sure if this is an issue?

I am a bit confused why this change caused the `test_keyboard_navigation_with_disabled_items` test to fail. This apparently expected the initial highlighted to be `None`, but this shouldn't be the case if the index is:

```python
# Set the index to the given initial index, or the first available index after.
```

I've updated that test accordingly.
TomJGooding: Sorry also to just to note, this assumes that "the first available index after" will wrap around. For example, where the `initial_index=8` in the added test, the highlighted index will be 1.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfebf364e..88847d849 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## Unreleased
+
+### Fixed
+
+- Fixed `ListView` bugs with the initial index https://github.com/Textualize/textual/pull/4452
+
+## Unreleased
+
+### Changed
+
+- When displaying a message using `App.exit()`, the console no longer highlights things such as numbers.
+
 
 ## [0.58.1] - 2024-05-01
 
@@ -14,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Fixed intermittent issue with scrolling to focus https://github.com/Textualize/textual/commit/567caf8acb196260adf6a0a6250e3ff5093056d0
 - Fixed issue with scrolling to center https://github.com/Textualize/textual/pull/4469
 
+
 ## [0.58.0] - 2024-04-25
 
 ### Fixed
diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md
index 9e24442d3..5747d394e 100644
--- a/docs/widgets/text_area.md
+++ b/docs/widgets/text_area.md
@@ -481,7 +481,7 @@ We can freely edit the text, and the syntax highlighting will update immediately
 Recall that we map names (like `@heading`) from the tree-sitter highlight query to Rich style objects inside the `TextAreaTheme.syntax_styles` dictionary.
 If you notice some highlights are missing after registering a language, the issue may be:
 
-1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new to `syntax_styles` should resolve the issue.
+1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new key-value pair to `syntax_styles` should resolve the issue.
 2. The highlight query doesn't assign a name to the pattern you expect to be highlighted. In this case you'll need to update the highlight query to assign to the name.
 
 !!! tip
diff --git a/src/textual/app.py b/src/textual/app.py
index ae4dab840..7c7c3fe38 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -453,7 +453,7 @@ class App(Generic[ReturnType], DOMNode):
             soft_wrap=False,
         )
         self._workers = WorkerManager(self)
-        self.error_console = Console(markup=False, stderr=True)
+        self.error_console = Console(markup=False, highlight=False, stderr=True)
         self.driver_class = driver_class or self.get_driver_class()
         self._screen_stacks: dict[str, list[Screen[Any]]] = {"_default": []}
         """A stack of screens per mode."""
diff --git a/src/textual/css/scalar.py b/src/textual/css/scalar.py
index 0ef332c57..849f56673 100644
--- a/src/textual/css/scalar.py
+++ b/src/textual/css/scalar.py
@@ -12,15 +12,15 @@ from ..geometry import Offset, Size, clamp
 
 
 class ScalarError(Exception):
-    pass
+    """Base class for exceptions raised by the Scalar class."""
 
 
 class ScalarResolveError(ScalarError):
-    pass
+    """Raised for errors resolving scalars (unlikely to occur in practice)."""
 
 
 class ScalarParseError(ScalarError):
-    pass
+    """Raised when a scalar couldn't be parsed from a string."""
 
 
 @unique
diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py
index bc51a9ead..77cfc75bc 100644
--- a/src/textual/widgets/_list_view.py
+++ b/src/textual/widgets/_list_view.py
@@ -119,9 +119,10 @@ class ListView(VerticalScroll, can_focus=True, can_focus_children=False):
         )
         # Set the index to the given initial index, or the first available index after.
         self._index = _widget_navigation.find_next_enabled(
-            self._nodes,
-            anchor=initial_index - 1 if initial_index is not None else None,
+            children,
+            anchor=initial_index if initial_index is not None else None,
             direction=1,
+            with_anchor=True,
         )
 
     def _on_mount(self, _: Mount) -> None:


Test patch:
diff --git a/tests/listview/test_listview_initial_index.py b/tests/listview/test_listview_initial_index.py
new file mode 100644
index 000000000..515de4f04
--- /dev/null
+++ b/tests/listview/test_listview_initial_index.py
@@ -0,0 +1,42 @@
+import pytest
+
+from textual.app import App, ComposeResult
+from textual.widgets import Label, ListItem, ListView
+
+
+@pytest.mark.parametrize(
+    "initial_index,expected_index",
+    [
+        (0, 1),
+        (1, 1),
+        (2, 4),
+        (3, 4),
+        (4, 4),
+        (5, 5),
+        (6, 7),
+        (7, 7),
+        (8, 1),
+    ],
+)
+async def test_listview_initial_index(initial_index, expected_index) -> None:
+    """Regression test for https://github.com/Textualize/textual/issues/4449"""
+
+    class ListViewDisabledItemsApp(App[None]):
+        def compose(self) -> ComposeResult:
+            yield ListView(
+                ListItem(Label("0"), disabled=True),
+                ListItem(Label("1")),
+                ListItem(Label("2"), disabled=True),
+                ListItem(Label("3"), disabled=True),
+                ListItem(Label("4")),
+                ListItem(Label("5")),
+                ListItem(Label("6"), disabled=True),
+                ListItem(Label("7")),
+                ListItem(Label("8"), disabled=True),
+                initial_index=initial_index,
+            )
+
+    app = ListViewDisabledItemsApp()
+    async with app.run_test() as pilot:
+        list_view = pilot.app.query_one(ListView)
+        assert list_view._index == expected_index
diff --git a/tests/listview/test_listview_navigation.py b/tests/listview/test_listview_navigation.py
index 4a93bb269..680b8ced6 100644
--- a/tests/listview/test_listview_navigation.py
+++ b/tests/listview/test_listview_navigation.py
@@ -35,7 +35,6 @@ async def test_keyboard_navigation_with_disabled_items() -> None:
             await pilot.press("up")
 
     assert app.highlighted == [
-        None,
         "1",
         "4",
         "5",