Repository: Textualize/textual
Pull request: Text log scroll end
Adds options to influence scrolling behaviour in TextLog

Fixes https://github.com/Textualize/textual/issues/2105

Problem statement:
`TextLog.write`: optional auto scroll
Hi,

`TextLog.write` always scrolls to the end. I use

```python
class TextLog(textual.widgets.TextLog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._auto_scroll = True
        self._writing = False

    def watch_scroll_y(self, *args, **kwargs):
        if len(args) < 2:
            return super().watch_scroll_y(*args, **kwargs)
        self._auto_scroll = args[1] == self.max_scroll_y
        return super().watch_scroll_y(*args, **kwargs)

    def write(self, *args, **kwargs):
        self._writing = True
        try:
            object = super().write(*args, **kwargs)
        finally:
            self._writing = False
        return object

    def scroll_end(self, *args, **kwargs):
        if self._writing and not self._auto_scroll:
            return
        return super().scroll_end(*args, **kwargs)
```

to disable auto scroll when scrolling off the last line (`max_scroll_y`), similar to most terminals, but would prefer an official solution.

Hints from issue discussion:
Thank you for your issue. Give us a little time to review it.

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

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)
We could add an argument to `write` to not scroll. Something like "scroll_end=False"

Commit message: tweak docstrings

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c94515f78..3d24d506a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
   - `TreeNode`: `expand`, `expand_all`, `collapse`, `collapse_all`, `toggle`, `toggle_all`
   - `Tree`: `clear`, `reset`
 
+### Added
+
+- Added auto_scroll attribute to TextLog https://github.com/Textualize/textual/pull/2127
+- Added scroll_end switch to TextLog.write https://github.com/Textualize/textual/pull/2127
 
 
 ## [0.16.0] - 2023-03-22
diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py
index bcf0923546..7d74a67891 100644
--- a/src/textual/widgets/_text_log.py
+++ b/src/textual/widgets/_text_log.py
@@ -38,6 +38,7 @@ class TextLog(ScrollView, can_focus=True):
     wrap: var[bool] = var(False)
     highlight: var[bool] = var(False)
     markup: var[bool] = var(False)
+    auto_scroll: var[bool] = var(True)
 
     def __init__(
         self,
@@ -47,6 +48,7 @@ def __init__(
         wrap: bool = False,
         highlight: bool = False,
         markup: bool = False,
+        auto_scroll: bool = True,
         name: str | None = None,
         id: str | None = None,
         classes: str | None = None,
@@ -60,6 +62,7 @@ def __init__(
             wrap: Enable word wrapping (default is off).
             highlight: Automatically highlight content.
             markup: Apply Rich console markup.
+            auto_scroll: Enable automatic scrolling to end.
             name: The name of the text log.
             id: The ID of the text log in the DOM.
             classes: The CSS classes of the text log.
@@ -81,6 +84,8 @@ def __init__(
         """Automatically highlight content."""
         self.markup = markup
         """Apply Rich console markup."""
+        self.auto_scroll = auto_scroll
+        """Automatically scroll to the end on write."""
         self.highlighter = ReprHighlighter()
 
     def notify_style_update(self) -> None:
@@ -92,19 +97,26 @@ def write(
         width: int | None = None,
         expand: bool = False,
         shrink: bool = True,
+        scroll_end: bool | None = None,
     ) -> Self:
         """Write text or a rich renderable.
 
         Args:
             content: Rich renderable (or text).
-            width: Width to render or ``None`` to use optimal width.
-            expand: Enable expand to widget width, or ``False`` to use `width`.
+            width: Width to render or `None` to use optimal width.
+            expand: Enable expand to widget width, or `False` to use `width`.
             shrink: Enable shrinking of content to fit width.
+            scroll_end: Enable automatic scroll to end, or `None` to use `self.auto_scroll`.
 
         Returns:
             The `TextLog` instance.
         """
 
+        if scroll_end is None:
+            auto_scroll = self.auto_scroll
+        else:
+            auto_scroll = scroll_end
+
         renderable: RenderableType
         if not is_renderable(content):
             renderable = Pretty(content)
@@ -158,7 +170,8 @@ def write(
             self.refresh()
             self.lines = self.lines[-self.max_lines :]
         self.virtual_size = Size(self.max_width, len(self.lines))
-        self.scroll_end(animate=False)
+        if auto_scroll:
+            self.scroll_end(animate=False)
 
         return self
 
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index ff7adbbd0e..f428174b98 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -18868,6 +18868,161 @@
   
   '''
 # ---
+# name: test_textlog_scroll
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-1988361344-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-1988361344-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-1988361344-r1 { fill: #e1e1e1 }
+  .terminal-1988361344-r2 { fill: #c5c8c6 }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-1988361344-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-1988361344-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1988361344-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-1988361344-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">TextLogScrollApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-1988361344-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="1.5" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="292.8" y="1.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="1.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="1.5" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="622.2" y="1.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="1.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="1.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="1.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="25.9" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="292.8" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="25.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="25.9" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="622.2" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="25.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="50.3" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="292.8" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="50.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="50.3" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="622.2" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="50.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="74.7" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="292.8" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="74.7" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="74.7" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="622.2" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="74.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="74.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="99.1" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="292.8" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="99.1" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="622.2" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="99.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="99.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="123.5" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="292.8" y="123.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="123.5" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="123.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="123.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="123.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="123.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="147.9" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="292.8" y="147.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="147.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="147.9" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="147.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="147.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="147.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="147.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="172.3" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="292.8" y="172.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="172.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="172.3" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="172.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="172.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="172.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="172.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="196.7" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="292.8" y="196.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="196.7" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="196.7" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="196.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="196.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="196.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="196.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="221.1" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="292.8" y="221.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="221.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="221.1" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="221.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="221.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="221.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="221.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-1988361344-matrix">
+      <text class="terminal-1988361344-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-1988361344-line-0)">Line&#160;0</text><text class="terminal-1988361344-r1" x="317.2" y="20" textLength="85.4" clip-path="url(#terminal-1988361344-line-0)">Line&#160;10</text><text class="terminal-1988361344-r1" x="646.6" y="20" textLength="73.2" clip-path="url(#terminal-1988361344-line-0)">Line&#160;0</text><text class="terminal-1988361344-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-1988361344-line-0)">
+  </text><text class="terminal-1988361344-r1" x="0" y="44.4" textLength="73.2" clip-path="url(#terminal-1988361344-line-1)">Line&#160;1</text><text class="terminal-1988361344-r1" x="317.2" y="44.4" textLength="85.4" clip-path="url(#terminal-1988361344-line-1)">Line&#160;11</text><text class="terminal-1988361344-r1" x="646.6" y="44.4" textLength="73.2" clip-path="url(#terminal-1988361344-line-1)">Line&#160;1</text><text class="terminal-1988361344-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-1988361344-line-1)">
+  </text><text class="terminal-1988361344-r1" x="0" y="68.8" textLength="73.2" clip-path="url(#terminal-1988361344-line-2)">Line&#160;2</text><text class="terminal-1988361344-r1" x="317.2" y="68.8" textLength="85.4" clip-path="url(#terminal-1988361344-line-2)">Line&#160;12</text><text class="terminal-1988361344-r1" x="646.6" y="68.8" textLength="73.2" clip-path="url(#terminal-1988361344-line-2)">Line&#160;2</text><text class="terminal-1988361344-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-1988361344-line-2)">
+  </text><text class="terminal-1988361344-r1" x="0" y="93.2" textLength="73.2" clip-path="url(#terminal-1988361344-line-3)">Line&#160;3</text><text class="terminal-1988361344-r1" x="317.2" y="93.2" textLength="85.4" clip-path="url(#terminal-1988361344-line-3)">Line&#160;13</text><text class="terminal-1988361344-r1" x="646.6" y="93.2" textLength="73.2" clip-path="url(#terminal-1988361344-line-3)">Line&#160;3</text><text class="terminal-1988361344-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-1988361344-line-3)">
+  </text><text class="terminal-1988361344-r1" x="0" y="117.6" textLength="73.2" clip-path="url(#terminal-1988361344-line-4)">Line&#160;4</text><text class="terminal-1988361344-r1" x="317.2" y="117.6" textLength="85.4" clip-path="url(#terminal-1988361344-line-4)">Line&#160;14</text><text class="terminal-1988361344-r1" x="646.6" y="117.6" textLength="73.2" clip-path="url(#terminal-1988361344-line-4)">Line&#160;4</text><text class="terminal-1988361344-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-1988361344-line-4)">
+  </text><text class="terminal-1988361344-r1" x="0" y="142" textLength="73.2" clip-path="url(#terminal-1988361344-line-5)">Line&#160;5</text><text class="terminal-1988361344-r1" x="317.2" y="142" textLength="85.4" clip-path="url(#terminal-1988361344-line-5)">Line&#160;15</text><text class="terminal-1988361344-r1" x="646.6" y="142" textLength="73.2" clip-path="url(#terminal-1988361344-line-5)">Line&#160;5</text><text class="terminal-1988361344-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-1988361344-line-5)">
+  </text><text class="terminal-1988361344-r1" x="0" y="166.4" textLength="73.2" clip-path="url(#terminal-1988361344-line-6)">Line&#160;6</text><text class="terminal-1988361344-r1" x="317.2" y="166.4" textLength="85.4" clip-path="url(#terminal-1988361344-line-6)">Line&#160;16</text><text class="terminal-1988361344-r1" x="646.6" y="166.4" textLength="73.2" clip-path="url(#terminal-1988361344-line-6)">Line&#160;6</text><text class="terminal-1988361344-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-1988361344-line-6)">
+  </text><text class="terminal-1988361344-r1" x="0" y="190.8" textLength="73.2" clip-path="url(#terminal-1988361344-line-7)">Line&#160;7</text><text class="terminal-1988361344-r1" x="317.2" y="190.8" textLength="85.4" clip-path="url(#terminal-1988361344-line-7)">Line&#160;17</text><text class="terminal-1988361344-r1" x="646.6" y="190.8" textLength="73.2" clip-path="url(#terminal-1988361344-line-7)">Line&#160;7</text><text class="terminal-1988361344-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-1988361344-line-7)">
+  </text><text class="terminal-1988361344-r1" x="0" y="215.2" textLength="73.2" clip-path="url(#terminal-1988361344-line-8)">Line&#160;8</text><text class="terminal-1988361344-r1" x="317.2" y="215.2" textLength="85.4" clip-path="url(#terminal-1988361344-line-8)">Line&#160;18</text><text class="terminal-1988361344-r1" x="646.6" y="215.2" textLength="73.2" clip-path="url(#terminal-1988361344-line-8)">Line&#160;8</text><text class="terminal-1988361344-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-1988361344-line-8)">
+  </text><text class="terminal-1988361344-r1" x="0" y="239.6" textLength="73.2" clip-path="url(#terminal-1988361344-line-9)">Line&#160;9</text><text class="terminal-1988361344-r1" x="317.2" y="239.6" textLength="85.4" clip-path="url(#terminal-1988361344-line-9)">Line&#160;19</text><text class="terminal-1988361344-r1" x="646.6" y="239.6" textLength="73.2" clip-path="url(#terminal-1988361344-line-9)">Line&#160;9</text><text class="terminal-1988361344-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-1988361344-line-9)">
+  </text><text class="terminal-1988361344-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-1988361344-line-10)">
+  </text><text class="terminal-1988361344-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-1988361344-line-11)">
+  </text><text class="terminal-1988361344-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-1988361344-line-12)">
+  </text><text class="terminal-1988361344-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-1988361344-line-13)">
+  </text><text class="terminal-1988361344-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-1988361344-line-14)">
+  </text><text class="terminal-1988361344-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-1988361344-line-15)">
+  </text><text class="terminal-1988361344-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-1988361344-line-16)">
+  </text><text class="terminal-1988361344-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-1988361344-line-17)">
+  </text><text class="terminal-1988361344-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-1988361344-line-18)">
+  </text><text class="terminal-1988361344-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-1988361344-line-19)">
+  </text><text class="terminal-1988361344-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-1988361344-line-20)">
+  </text><text class="terminal-1988361344-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-1988361344-line-21)">
+  </text><text class="terminal-1988361344-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-1988361344-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_tree_example
   '''
   <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
diff --git a/tests/snapshot_tests/snapshot_apps/textlog_scroll.py b/tests/snapshot_tests/snapshot_apps/textlog_scroll.py
new file mode 100644
index 0000000000..d0eed5b819
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/textlog_scroll.py
@@ -0,0 +1,35 @@
+from textual.app import App, ComposeResult
+from textual.widgets import TextLog
+from textual.containers import Horizontal
+
+
+class TextLogScrollApp(App):
+    CSS = """
+    TextLog{
+        width: 1fr;
+        height: 10;
+    }
+    """
+
+    def compose(self) -> ComposeResult:
+        with Horizontal():
+            # Don't scroll on write
+            yield TextLog(id="textlog1", auto_scroll=False)
+            # Scroll on write
+            yield TextLog(id="textlog2", auto_scroll=True)
+            # Scroll on write, but disabled on write()
+            yield TextLog(id="textlog3", auto_scroll=True)
+
+    def on_ready(self) -> None:
+        lines = [f"Line {n}" for n in range(20)]
+        for line in lines:
+            self.query_one("#textlog1", TextLog).write(line)
+        for line in lines:
+            self.query_one("#textlog2", TextLog).write(line)
+        for line in lines:
+            self.query_one("#textlog3", TextLog).write(line, scroll_end=False)
+
+
+if __name__ == "__main__":
+    app = TextLogScrollApp()
+    app.run()
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
index b7b003347c..94972325c5 100644
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -323,3 +323,7 @@ def test_auto_table(snap_compare):
 
 def test_table_markup(snap_compare):
     assert snap_compare(SNAPSHOT_APPS_DIR / "table_markup.py")
+
+
+def test_textlog_scroll(snap_compare):
+    assert snap_compare(SNAPSHOT_APPS_DIR / "textlog_scroll.py")


Code review comments:
diff_hunk: @@ -92,19 +97,26 @@ def write(
         width: int | None = None,
         expand: bool = False,
         shrink: bool = True,
+        scroll_end: bool | None = None,
     ) -> Self:
         """Write text or a rich renderable.
 
         Args:
             content: Rich renderable (or text).
-            width: Width to render or ``None`` to use optimal width.
-            expand: Enable expand to widget width, or ``False`` to use `width`.
+            width: Width to render or `None` to use optimal width.
+            expand: Enable expand to widget width, or `False` to use `width`.
             shrink: Enable shrinking of content to fit width.
+            scroll_end: Enable automatic scroll to end, or `None` to use `self.auto_scroll`.
 
         Returns:
             The `TextLog` instance.
         """
 
+        if scroll_end is None:
+            auto_scroll = self.auto_scroll
+        else:
+            auto_scroll = scroll_end
path: src/textual/widgets/_text_log.py
text: @user1:
I thought we liked conditional expressions?

How do you typically determine whether you'll use a conditional expression or an `if: ... else: ...`?

```suggestion
        auto_scroll = self.auto_scroll if scroll_end is None else scroll_end
```

@author:
I think it is just a stylistic thing for the most part.