Repository: Textualize/textual

Issue:
Markup colors are getting reset with a newline character
### Markup colors are being reset after a newline character
An example of output showcasing the bug.
The second line should be white
<img width="199" alt="Image" src="https://github.com/user-attachments/assets/d5ef7472-2df0-4a42-9100-3dc4cce054db" />

### Example program to demonstrate bug:
```from textual.app import App, ComposeResult
from textual.widgets import Label


class LabelApp(App):
    def compose(self) -> ComposeResult:
        yield Label("[#ff0000]Hello, [#ffffff]world!\nMy work here is done.")


if __name__ == "__main__":
    app = LabelApp()
    app.run()
```
### What I expect
The second and subsequent lines should be white.

# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 3.2.0  |
| Rich    | 14.0.0 |

## Python

| Name           | Value                                            |
|----------------|--------------------------------------------------|
| Version        | 3.13.3                                           |
| Implementation | CPython                                          |
| Compiler       | Clang 16.0.0 (clang-1600.0.26.6)                 |
| Executable     | /Users/djp3/Downloads/temp/sample/bin/python3.13 |

## Operating System

| Name    | Value                                                                                                  |
|---------|--------------------------------------------------------------------------------------------------------|
| System  | Darwin                                                                                                 |
| Release | 24.5.0                                                                                                 |
| Version | Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041 |

## Terminal

| Name                 | Value              |
|----------------------|--------------------|
| Terminal Application | iTerm.app (3.5.13) |
| TERM                 | xterm-256color     |
| COLORTERM            | truecolor          |
| FORCE_COLOR          | *Not set*          |
| NO_COLOR             | *Not set*          |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=212, height=79 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 212                  |
| is_terminal    | True                 |
| encoding       | utf-8                |
| max_height     | 79                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |


# Thanks for working on this framework.  It rocks!


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


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

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)
Don't forget to [star](https://github.com/Textualize/textual) the repository!

Follow [@textualizeio](https://twitter.com/textualizeio) for Textual updates.



Patch:
diff --git a/src/textual/_markup_playground.py b/src/textual/_markup_playground.py
index 720e2d68d4..5005016111 100644
--- a/src/textual/_markup_playground.py
+++ b/src/textual/_markup_playground.py
@@ -4,25 +4,21 @@
 from textual.app import App, ComposeResult
 from textual.content import Content
 from textual.reactive import reactive
-from textual.widgets import Static, TextArea
+from textual.widgets import Footer, Pretty, Static, TextArea
 
 
 class MarkupPlayground(App):
 
     TITLE = "Markup Playground"
     CSS = """
-    Screen {
-        & > * {
-            margin: 0 1;
-            height: 1fr;
-        }
+    Screen {        
         layout: vertical;
         #editor {            
-            width: 2fr;
+            width: 1fr;
             height: 1fr;
             border: tab $foreground 50%;  
             padding: 1;
-            margin: 1 1 0 0;
+            margin: 1 0 0 0;
             &:focus {
                 border: tab $primary;  
             }
@@ -48,16 +44,35 @@ class MarkupPlayground(App):
             }
             overflow-y: auto;
         }
-        #results {            
-            
+        #results {                        
             padding: 1 1;            
+            width: 1fr;
+        }
+        #spans-container {
+            border: tab $success;                
+            overflow-y: auto;
+            margin: 0 0 0 1;
+        }
+        #spans {
+            padding: 1 1;      
+            width: 1fr;                
+        }
+        HorizontalGroup {
+            height: 1fr;
         }
     }
     """
     AUTO_FOCUS = "#editor"
 
+    BINDINGS = [
+        ("f1", "toggle('show_variables')", "Variables"),
+        ("f2", "toggle('show_spans')", "Spans"),
+    ]
     variables: reactive[dict[str, object]] = reactive({})
 
+    show_variables = reactive(False)
+    show_spans = reactive(False)
+
     def compose(self) -> ComposeResult:
         with containers.HorizontalGroup():
             yield (editor := TextArea(id="editor"))
@@ -65,11 +80,21 @@ def compose(self) -> ComposeResult:
         editor.border_title = "Markup"
         variables.border_title = "Variables (JSON)"
 
-        with containers.VerticalScroll(
-            id="results-container", can_focus=False
-        ) as container:
-            yield Static(id="results")
-        container.border_title = "Output"
+        with containers.HorizontalGroup():
+            with containers.VerticalScroll(id="results-container") as container:
+                yield Static(id="results")
+                container.border_title = "Output"
+            with containers.VerticalScroll(id="spans-container") as container:
+                yield Pretty([], id="spans")
+                container.border_title = "Spans"
+
+        yield Footer()
+
+    def watch_show_variables(self, show_variables: bool) -> None:
+        self.query_one("#variables").display = show_variables
+
+    def watch_show_spans(self, show_spans: bool) -> None:
+        self.query_one("#spans-container").display = show_spans
 
     @on(TextArea.Changed, "#editor")
     def on_markup_changed(self, event: TextArea.Changed) -> None:
@@ -78,13 +103,16 @@ def on_markup_changed(self, event: TextArea.Changed) -> None:
     def update_markup(self) -> None:
         results = self.query_one("#results", Static)
         editor = self.query_one("#editor", TextArea)
+        spans = self.query_one("#spans", Pretty)
         try:
             content = Content.from_markup(editor.text, **self.variables)
             results.update(content)
-        except Exception as error:
+            spans.update(content.spans)
+        except Exception:
             from rich.traceback import Traceback
 
             results.update(Traceback())
+            spans.update([])
 
             self.query_one("#results-container").add_class("-error").scroll_end(
                 animate=False
diff --git a/src/textual/markup.py b/src/textual/markup.py
index 2d92ae7353..06a0effd98 100644
--- a/src/textual/markup.py
+++ b/src/textual/markup.py
@@ -411,14 +411,13 @@ def process_text(template_text: str, /) -> str:
 
     content_text = "".join(text)
     text_length = len(content_text)
-    while style_stack:
-        position, tag_body, _ = style_stack.pop()
+    for position, tag_body, _ in style_stack:
         spans.append(Span(position, text_length, tag_body))
 
-    if style:
-        content = Content(content_text, [Span(0, len(content_text), style), *spans])
-    else:
-        content = Content(content_text, spans)
+    content = Content(
+        content_text,
+        [Span(0, len(content_text), style), *spans] if style else spans,
+    )
 
     return content
 


Test patch:
diff --git a/tests/test_markup.py b/tests/test_markup.py
index 32032bf5bd..718c61752c 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -77,6 +77,13 @@
                 ],
             ),
         ),
+        (
+            "[#ff0000]Hello, [#ffffff]world!\nMy work here is done.",
+            Content(
+                "Hello, world!\nMy work here is done.",
+                spans=[Span(0, 35, style="#ff0000"), Span(7, 35, style="#ffffff")],
+            ),
+        ),
     ],
 )
 def test_to_content(markup: str, content: Content):