Repository: Textualize/rich

Issue:
[BUG] Invalid markup in a ProgressBar causes the entire Python script to exit abnormally.
**Describe the bug**

**NOTE: I found some more details on this. The issue isn't that an exception isn't raised, it's that you can't ever see any console output from that exception. Alternate screen issue?** See EDIT below. Original bug report follows.

If you try to create a ProgressBar object, and within your fields you have some invalid markup (example: a closing [/color] tag without a corresponding opening tag), starting the progress bar with `start()` will *cause the entire Python interpreter to exit with return code 1*. 

No error message is printed. Wrapping the code in a try/except block does not trap the error, the entire script still exits.

The following is a minimal working example illustrating the bug.

    from rich.progress import (
        BarColumn,
        Progress,
        TaskID,
        TextColumn,
        TimeRemainingColumn,
    )

    import time

    def test():

        print("I will now make Rich fail horribly...")

        pbar = Progress(
            "[green]Status",
            TimeRemainingColumn(),
            "Running[/red]" # THIS LINE HAS INVALID MARKUP. IT WILL CAUSE THE SCRIPT TO CRASH WITH NO ERROR.
        )

        task_id = pbar.add_task("test",start=False, total=10)

        # PROGRAM EXECUTION ABORTS HERE WITH NO ERROR MESSAGES.
        # Python interpreter returns code 1.
        pbar.start()

        print("We should make it here, but we don't.")

        for _ in range(10):
            time.sleep()
            pbar.update(task_id, advance=1)

        pbar.stop()

    if __name__ == "__main__":

        # Even wrapping the test in a try/catch block does not prevent Python from exiting!
        try:
            test()
        except Exception as e:
            print(f"I caught an exception! {e}") # This is NOT called, NO exception is raised.

        print("I made it through the test!") # This is also NEVER reached. The script EXITS when pbar.start() is called.

Example run (Not much to see...):

    dev@devbox:~$ python3 richbug.py
    dev@devbox:~$ echo $?
    1
    dev@devbox:~$

Note that I have not tested this further to determine if it happens in other areas of Rich, but I know for sure it happens with ProgressBar.

**What should happen?**

If there's invalid markup, a normal exception should get thrown somewhere. 

Even if for some reason the app needs to fully exit, printing an error message would still be useful. I spent over an hour tracking down what I thought was a bug or a forgotten exit() call in my own code before realizing the exact line where things failed was `pbar.start()`.

I have a *suspicion* that this might have to do with the alternate screen - perhaps an exception is printed but it's done on the alternate screen so you never see it? I haven't spent much time looking at Rich's code, but I'd imagine perhaps wrapping code in try blocks with code to exit the alternate screen followed by re-raising the exception might work?

One more point: after the code exits, the cursor is missing - I have to use `reset` to bring it back. Again, suggests that we're switching into the alternate screen, crashing and then not getting back out to print errors.

**EDIT: I discovered this is indeed the case. If I add the line `open("exception.txt","w").write(str(e))` to the except block, the exception does get printed and indeed does include the correct markup error. So therefore this bug should perhaps be named "App does not exit alternate screen before crashing"?**

**Platform**

```
dev@devbox:~$ python -m rich.diagnose

    ╭───────────────────────── <class 'rich.console.Console'> ─────────────────────────╮
    │ A high level console interface.                                                  │
    │                                                                                  │
    │ ╭──────────────────────────────────────────────────────────────────────────────╮ │
    │ │ <console width=148 ColorSystem.STANDARD>                                     │ │
    │ ╰──────────────────────────────────────────────────────────────────────────────╯ │
    │                                                                                  │
    │     color_system = 'standard'                                                    │
    │         encoding = 'utf-8'                                                       │
    │             file = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> │
    │           height = 30                                                            │
    │    is_alt_screen = False                                                         │
    │ is_dumb_terminal = False                                                         │
    │   is_interactive = True                                                          │
    │       is_jupyter = False                                                         │
    │      is_terminal = True                                                          │
    │   legacy_windows = False                                                         │
    │         no_color = False                                                         │
    │          options = ConsoleOptions(                                               │
    │                        size=ConsoleDimensions(width=148, height=30),             │
    │                        legacy_windows=False,                                     │
    │                        min_width=1,                                              │
    │                        max_width=148,                                            │
    │                        is_terminal=True,                                         │
    │                        encoding='utf-8',                                         │
    │                        max_height=30,                                            │
    │                        justify=None,                                             │
    │                        overflow=None,                                            │
    │                        no_wrap=False,                                            │
    │                        highlight=None,                                           │
    │                        markup=None,                                              │
    │                        height=None                                               │
    │                    )                                                             │
    │            quiet = False                                                         │
    │           record = False                                                         │
    │         safe_box = True                                                          │
    │             size = ConsoleDimensions(width=148, height=30)                       │
    │        soft_wrap = False                                                         │
    │           stderr = False                                                         │
    │            style = None                                                          │
    │         tab_size = 8                                                             │
    │            width = 148                                                           │
    ╰──────────────────────────────────────────────────────────────────────────────────╯
    ╭─── <class 'rich._windows.WindowsConsoleFeatures'> ────╮
    │ Windows features available.                           │
    │                                                       │
    │ ╭───────────────────────────────────────────────────╮ │
    │ │ WindowsConsoleFeatures(vt=False, truecolor=False) │ │
    │ ╰───────────────────────────────────────────────────╯ │
    │                                                       │
    │ truecolor = False                                     │
    │        vt = False                                     │
    ╰───────────────────────────────────────────────────────╯
    ╭────── Environment Variables ───────╮
    │ {                                  │
    │     'TERM': 'screen',              │
    │     'COLORTERM': None,             │
    │     'CLICOLOR': None,              │
    │     'NO_COLOR': None,              │
    │     'TERM_PROGRAM': None,          │
    │     'COLUMNS': None,               │
    │     'LINES': None,                 │
    │     'JPY_PARENT_PID': None,        │
    │     'VSCODE_VERBOSE_LOGGING': None │
    │ }                                  │
    ╰────────────────────────────────────╯
    platform="Linux"

dev@devbox:~$ pip freeze | grep rich
rich==12.4.4
```

The above was run while SSH'ed into the devbox from Windows terminal. Same issue will occur no matter what client is being used though. Happens no matter how I run the code, whether it be on a local terminal, via SSH, etc. 

Devbox is running Ubuntu Linux 22.04.



Discussion:

Resolving patch:
diff --git a/rich/live.py b/rich/live.py
--- a/rich/live.py
+++ b/rich/live.py
@@ -118,7 +118,15 @@ def start(self, refresh: bool = False) -> None:
             self._enable_redirect_io()
             self.console.push_render_hook(self)
             if refresh:
-                self.refresh()
+                try:
+                    self.refresh()
+                except Exception:
+                    # If refresh fails, we want to stop the redirection of sys.stderr,
+                    # so the error stacktrace is properly displayed in the terminal.
+                    # (or, if the code that calls Rich captures the exception and wants to display something,
+                    # let this be displayed in the terminal).
+                    self.stop()
+                    raise
             if self.auto_refresh:
                 self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
                 self._refresh_thread.start()


Test patch: